feat: implement ws

This commit is contained in:
Thibaut Valentin 2025-11-21 14:03:06 +01:00
parent a83088387b
commit b78b3f005b
2 changed files with 89 additions and 47 deletions

View File

@ -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.SirenService".url=https://data.siren-api.fr/
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.StateIdService".url=https://www.data-asso.fr/api/ 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 #Login
quarkus.oidc.token-state-manager.split-tokens=true quarkus.oidc.token-state-manager.split-tokens=true
quarkus.oidc.token.refresh-expired=true quarkus.oidc.token.refresh-expired=true

View File

@ -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() { function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => 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}) { export function WSProvider({url, onmessage, children}) {
const id = useId();
const [isReady, setIsReady] = useState(false) const [isReady, setIsReady] = useState(false)
const [state, dispatch] = useReducer(reducer, {listener: []}) const [state, dispatch] = useReducer(reducer, {listener: []})
const ws = useRef(null) const ws = useRef(null)
@ -49,57 +51,104 @@ export function WSProvider({url, onmessage, children}) {
}, [state.listener]) }, [state.listener])
useEffect(() => { useEffect(() => {
console.log("WSProvider: connecting to", url); if (!mountCounter[id])
const socket = new WebSocket(url) mountCounter[id] = 0
mountCounter[id] += 1
console.log(`WSProvider ${id} mounted ${mountCounter[id]} time(s)`);
socket.onopen = () => setIsReady(true) if (mountCounter[id] === 1 && (ws.current === null || ws.current.readyState >= WebSocket.CLOSING)){
socket.onclose = () => setIsReady(false) console.log("WSProvider: connecting to", url);
socket.onmessage = (event) => { const socket = new WebSocket(url)
const msg = JSON.parse(event.data)
if (msg.type === "REPLY" || msg.type === "ERROR") { socket.onopen = () => setIsReady(true)
const cb = callbackRef.current[msg.uuid]; socket.onclose = () => {
if (cb) { setIsReady(false)
if (msg.type === "REPLY") if (mountCounter[id] > 0) {
cb.onReply(msg.data); console.log("WSProvider: reconnecting to", url);
else setTimeout(() => {
cb.onError(msg.data); try {
delete callbackRef.current[msg.uuid] 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; ws.current = socket
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
return () => { 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 = () => { const send = (uuid, code, type, data, resolve = () => {
}, onError = () => { }, reject = () => {
}) => { }) => {
if (!isReady) { if (!isReady) {
onError("WebSocket is not connected"); reject("WebSocket is not connected");
return; return;
} }
if (type === "REQUEST") { 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({ ws.current?.send(JSON.stringify({
uuid: uuid, uuid: uuid,
@ -124,17 +173,7 @@ export function useWS() {
wait_length, wait_length,
sendRequest: (code, data) => { sendRequest: (code, data) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { send(uuidv4(), code, "REQUEST", data, resolve, reject);
reject("timeout");
}, 1000);
send(uuidv4(), code, "REQUEST", data, (data) => {
clearTimeout(timeout);
resolve(data);
}, (data) => {
clearTimeout(timeout);
reject(data);
});
}) })
}, },
sendNotify: (code, data) => { sendNotify: (code, data) => {