From b78b3f005b50e6cf5b4bb6052fdb26dca4251fee Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Fri, 21 Nov 2025 14:03:06 +0100 Subject: [PATCH] feat: implement ws --- src/main/resources/application.properties | 3 + src/main/webapp/src/hooks/useWS.jsx | 133 ++++++++++++++-------- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 984ee85..0d7d3e6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -45,6 +45,9 @@ siren-api.key=siren-ap quarkus.rest-client."fr.titionfire.ffsaf.rest.client.SirenService".url=https://data.siren-api.fr/ quarkus.rest-client."fr.titionfire.ffsaf.rest.client.StateIdService".url=https://www.data-asso.fr/api/ +quarkus.websockets-next.server.auto-ping-interval=PT10s +quarkus.websockets-next.client.connection-idle-timeout=PT30s + #Login quarkus.oidc.token-state-manager.split-tokens=true quarkus.oidc.token.refresh-expired=true diff --git a/src/main/webapp/src/hooks/useWS.jsx b/src/main/webapp/src/hooks/useWS.jsx index e475704..3ef5f9d 100644 --- a/src/main/webapp/src/hooks/useWS.jsx +++ b/src/main/webapp/src/hooks/useWS.jsx @@ -1,4 +1,4 @@ -import {createContext, useContext, useEffect, useReducer, useRef, useState} from "react"; +import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react"; function uuidv4() { return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => @@ -36,8 +36,10 @@ function reducer(state, action) { } } +const mountCounter = {}; export function WSProvider({url, onmessage, children}) { + const id = useId(); const [isReady, setIsReady] = useState(false) const [state, dispatch] = useReducer(reducer, {listener: []}) const ws = useRef(null) @@ -49,57 +51,104 @@ export function WSProvider({url, onmessage, children}) { }, [state.listener]) useEffect(() => { - console.log("WSProvider: connecting to", url); - const socket = new WebSocket(url) + if (!mountCounter[id]) + mountCounter[id] = 0 + mountCounter[id] += 1 + console.log(`WSProvider ${id} mounted ${mountCounter[id]} time(s)`); - socket.onopen = () => setIsReady(true) - socket.onclose = () => setIsReady(false) - socket.onmessage = (event) => { - const msg = JSON.parse(event.data) + if (mountCounter[id] === 1 && (ws.current === null || ws.current.readyState >= WebSocket.CLOSING)){ + console.log("WSProvider: connecting to", url); + const socket = new WebSocket(url) - if (msg.type === "REPLY" || msg.type === "ERROR") { - const cb = callbackRef.current[msg.uuid]; - if (cb) { - if (msg.type === "REPLY") - cb.onReply(msg.data); - else - cb.onError(msg.data); - delete callbackRef.current[msg.uuid] + socket.onopen = () => setIsReady(true) + socket.onclose = () => { + setIsReady(false) + if (mountCounter[id] > 0) { + console.log("WSProvider: reconnecting to", url); + setTimeout(() => { + try { + const newSocket = new WebSocket(url) + ws.current = newSocket + newSocket.onopen = socket.onopen + newSocket.onclose = socket.onclose + newSocket.onmessage = socket.onmessage + }catch (e) { + + } + }, 5000) } - return; + } + socket.onmessage = (event) => { + const msg = JSON.parse(event.data) + + if (msg.type === "REPLY" || msg.type === "ERROR") { + const cb = callbackRef.current[msg.uuid]; + if (cb) { + if (msg.type === "REPLY") + cb.onReply(msg.data); + else + cb.onError(msg.data); + delete callbackRef.current[msg.uuid] + } + return; + } + + let isHandled = false; + + listenersRef.current + .filter(l => l.code === msg.code) + .forEach(l => { + try { + l.callback({...msg}) + isHandled = true; + } catch (err) { + console.error("Listener callback error:", err) + } + }); + if (!isHandled && onmessage) + onmessage(JSON.parse(event.data)) } - let isHandled = false; - - listenersRef.current - .filter(l => l.code === msg.code) - .forEach(l => { - try { - l.callback({...msg}) - isHandled = true; - } catch (err) { - console.error("Listener callback error:", err) - } - }); - if (!isHandled && onmessage) - onmessage(JSON.parse(event.data)) + ws.current = socket } - ws.current = socket return () => { - socket.close() + mountCounter[id] -= 1 + console.log(`WSProvider ${id} unmounted, ${mountCounter[id]} instance(s) remain`); + + setTimeout(() => { + console.log(`WSProvider ${id} checking for close, ${mountCounter[id]} instance(s) remain`); + if (mountCounter[id] === 0) { + console.log("WSProvider: closing connection to", url); + ws.current.close() + } + }, 250) } }, []) - const send = (uuid, code, type, data, onReply = () => { - }, onError = () => { + const send = (uuid, code, type, data, resolve = () => { + }, reject = () => { }) => { if (!isReady) { - onError("WebSocket is not connected"); + reject("WebSocket is not connected"); return; } if (type === "REQUEST") { - callbackRef.current[uuid] = {onReply: onReply, onError: onError} + const timeout = setTimeout(() => { + reject("timeout"); + delete callbackRef.current[uuid]; + }, 30000); + callbackRef.current[uuid] = { + onReply: (data) => { + clearTimeout(timeout); + resolve(data); + }, + onError: (data) => { + clearTimeout(timeout); + reject(data); + } + + } } ws.current?.send(JSON.stringify({ uuid: uuid, @@ -124,17 +173,7 @@ export function useWS() { wait_length, sendRequest: (code, data) => { return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject("timeout"); - }, 1000); - - send(uuidv4(), code, "REQUEST", data, (data) => { - clearTimeout(timeout); - resolve(data); - }, (data) => { - clearTimeout(timeout); - reject(data); - }); + send(uuidv4(), code, "REQUEST", data, resolve, reject); }) }, sendNotify: (code, data) => {