mirror of
https://github.com/Steffo99/todocolors.git
synced 2024-11-22 08:14:18 +00:00
Fix websocket backoff mechanisms
This commit is contained in:
parent
550b2e6c51
commit
5982352792
10 changed files with 64 additions and 43 deletions
26
todoblue/src/app/[lang]/(page)/RootMain.module.css
Normal file
26
todoblue/src/app/[lang]/(page)/RootMain.module.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
.rootMain {
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.rootMain h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.rootMain > :global(.chapter-2) {
|
||||
max-width: 976px;
|
||||
}
|
||||
|
||||
.rootMain small {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.rootMain :global(.panel) {
|
||||
max-width: 484px;
|
||||
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import {CreateBoardChapter} from "@/app/[lang]/(page)/CreateBoardChapter"
|
||||
import {ExistingBoardChapter} from "@/app/[lang]/(page)/ExistingBoardChapter"
|
||||
import style from "@/app/[lang]/page.module.css"
|
||||
import style from "./RootMain.module.css"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export async function RootMain({lng}: {lng: string}) {
|
||||
return (
|
||||
<main className={style.pageMain}>
|
||||
<main className={style.rootMain}>
|
||||
<CreateBoardChapter lng={lng}/>
|
||||
<ExistingBoardChapter lng={lng}/>
|
||||
</main>
|
||||
|
|
|
@ -6,7 +6,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
|||
|
||||
|
||||
export function BoardMain({className}: {className?: string}) {
|
||||
const {webSocketState} = useManagedBoard()
|
||||
const {webSocketState, webSocketBackoffMs} = useManagedBoard()
|
||||
|
||||
switch(webSocketState) {
|
||||
case undefined:
|
||||
|
@ -18,6 +18,6 @@ export function BoardMain({className}: {className?: string}) {
|
|||
case WebSocket.CLOSING:
|
||||
return <BoardMainIcon icon={<FontAwesomeIcon size={"4x"} icon={faLinkSlash} beatFade/>} text={"Disconnessione..."} className={className}/>
|
||||
case WebSocket.CLOSED:
|
||||
return <BoardMainIcon icon={<FontAwesomeIcon size={"4x"} icon={faLinkSlash}/>} text={"Disconnesso"} className={className}/>
|
||||
return <BoardMainIcon icon={<FontAwesomeIcon size={"4x"} icon={faLinkSlash}/>} text={`Disconnesso, riconnessione tra ${webSocketBackoffMs}ms`} className={className}/>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface UseBoardReturns {
|
|||
tasksById: {[id: string]: Task},
|
||||
taskGroups: TaskGroup[],
|
||||
webSocketState: number | undefined,
|
||||
webSocketBackoffMs: number,
|
||||
isEditingTitle: boolean,
|
||||
stopEditingTitle: () => void,
|
||||
startEditingTitle: () => void,
|
||||
|
@ -38,7 +39,7 @@ export interface UseBoardReturns {
|
|||
}
|
||||
|
||||
export function useBoard(name: string): UseBoardReturns {
|
||||
const {state: {title, tasksById}, sendAction, webSocketState} = useBoardWs(name);
|
||||
const {state: {title, tasksById}, sendAction, webSocketState, webSocketBackoffMs} = useBoardWs(name);
|
||||
|
||||
const {value: [taskGrouper, groupSorter, groupNamer], move: moveGrouper, next: nextGrouper, previous: previousGrouper} = useCycleState(TASK_GROUPERS);
|
||||
const {value: taskSorter, move: moveSorter, next: nextSorter, previous: previousSorter} = useCycleState(TASK_SORTERS);
|
||||
|
@ -56,6 +57,7 @@ export function useBoard(name: string): UseBoardReturns {
|
|||
tasksById,
|
||||
taskGroups,
|
||||
webSocketState,
|
||||
webSocketBackoffMs,
|
||||
isEditingTitle,
|
||||
stopEditingTitle,
|
||||
startEditingTitle,
|
||||
|
|
|
@ -13,7 +13,7 @@ export function useBoardWs(name: string) {
|
|||
|
||||
const {state, act} = useBoardState();
|
||||
|
||||
const {webSocket, webSocketState} = useWs(wsFullURL, {
|
||||
const {webSocket, webSocketState, webSocketBackoffMs} = useWs(wsFullURL, {
|
||||
onopen: useCallback(({}) => {
|
||||
console.debug("[useBoardWs] Connected to board:", name);
|
||||
act(null);
|
||||
|
@ -27,9 +27,8 @@ export function useBoardWs(name: string) {
|
|||
console.error("[useBoardWs] Encountered a WebSocket error, closing current connection:", event);
|
||||
closeWebSocket()
|
||||
}, []),
|
||||
onclose: useCallback(({event, openWebSocket}: WebSocketHandlerParams<Event>) => {
|
||||
console.debug("[useBoardWs] WebSocket was closed, trying to reconnect:", event);
|
||||
openWebSocket()
|
||||
onclose: useCallback(({event}: WebSocketHandlerParams<Event>) => {
|
||||
console.debug("[useBoardWs] WebSocket was closed:", event);
|
||||
}, [])
|
||||
});
|
||||
|
||||
|
@ -42,5 +41,5 @@ export function useBoardWs(name: string) {
|
|||
webSocket.send(JSON.stringify(data));
|
||||
}, [webSocket, webSocketState])
|
||||
|
||||
return {state, sendAction, webSocketState}
|
||||
return {state, sendAction, webSocketState, webSocketBackoffMs}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
import "./layout.css";
|
||||
import {Body} from "@/app/[lang]/Body"
|
||||
import {Body} from "@/app/[lang]/(layout)/Body"
|
||||
import {StarredManager} from "@/app/[lang]/StarContext"
|
||||
import type {Metadata as NextMetadata} from "next"
|
||||
import {default as React, ReactNode} from "react"
|
||||
|
|
|
@ -9,11 +9,3 @@
|
|||
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.pageMain h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.pageMain div {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import {useState, useCallback, useEffect} from "react"
|
|||
|
||||
export interface WebSocketHandlerParams<E extends Event> {
|
||||
event: E,
|
||||
openWebSocket: () => void,
|
||||
openWebSocketAfterBackoff: () => void,
|
||||
closeWebSocket: () => void,
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ export interface WebSocketHandlers {
|
|||
export function useWs(url: string | undefined, {onclose, onerror, onmessage, onopen}: WebSocketHandlers) {
|
||||
const [webSocket, setWebSocket] = useState<WebSocket | undefined>(undefined)
|
||||
const [webSocketState, setWebSocketState] = useState<number | undefined>(undefined);
|
||||
const [isBackingOff, setBackingOff] = useState<boolean>(false);
|
||||
const [webSocketBackoffMs, setWebSocketBackoffMs] = useState<number>(1);
|
||||
|
||||
const closeWebSocket = useCallback(() => {
|
||||
|
@ -36,56 +37,57 @@ export function useWs(url: string | undefined, {onclose, onerror, onmessage, ono
|
|||
setWebSocketState(WebSocket.CLOSED);
|
||||
}, [webSocket])
|
||||
|
||||
const backOff = useCallback((func: () => void) => async () => {
|
||||
// This WILL cause no-ops, but they're going to be pretty infrequent, so idc
|
||||
console.debug("[useWebSocket] Backing off for:", webSocketBackoffMs, "ms")
|
||||
setBackingOff(true)
|
||||
await new Promise(resolve => setTimeout(resolve, webSocketBackoffMs))
|
||||
setBackingOff(false)
|
||||
func()
|
||||
}, [webSocketBackoffMs])
|
||||
|
||||
const openWebSocket = useCallback(() => {
|
||||
console.debug("[useWebSocket] Opening WebSocket:", url);
|
||||
if(url === undefined) {
|
||||
console.warn("[useWebSocket] Trying to open WebSocket, but no URL has been given; ignoring request...")
|
||||
return;
|
||||
}
|
||||
setWebSocketBackoffMs(prev => prev * 2); // Workaround for connections that get closed immediately by the server
|
||||
setWebSocketState(WebSocket.CONNECTING); // Workaround for constructor blocking and giving no feedback to the user
|
||||
const sock = new WebSocket(url);
|
||||
const openWebSocketAfterBackoff = backOff(openWebSocket);
|
||||
sock.onopen = (event) => {
|
||||
console.debug("[useWebSocket] Opened connection:", event)
|
||||
setWebSocket(sock)
|
||||
setWebSocketState(sock.readyState)
|
||||
setWebSocketBackoffMs(1)
|
||||
onopen?.({event, openWebSocket, closeWebSocket});
|
||||
onopen?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||
}
|
||||
sock.onclose = (event) => {
|
||||
console.debug("[useWebSocket] Closed connection:", event)
|
||||
setWebSocket(undefined)
|
||||
setWebSocketState(sock.readyState);
|
||||
onclose?.({event, openWebSocket, closeWebSocket});
|
||||
onclose?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||
}
|
||||
sock.onerror = (event) => {
|
||||
console.error("[useWebSocket] Error in connection:", event)
|
||||
setWebSocketState(sock.readyState)
|
||||
setWebSocketBackoffMs(prev => prev * 2)
|
||||
onerror?.({event, openWebSocket, closeWebSocket});
|
||||
onerror?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||
}
|
||||
sock.onmessage = (event) => {
|
||||
console.debug("[useWebSocket] Received message:", event)
|
||||
onmessage?.({event, openWebSocket, closeWebSocket});
|
||||
onmessage?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||
}
|
||||
}, [url, onopen, onclose, onerror, onmessage])
|
||||
|
||||
// @ts-ignore
|
||||
const openWebSocketAfterBackoff = useCallback(() => {
|
||||
console.debug("[useWebSocket] Backing off for:", webSocketBackoffMs, "ms")
|
||||
return setTimeout(openWebSocket, webSocketBackoffMs)
|
||||
}, [openWebSocket, webSocketBackoffMs])
|
||||
}, [url, onopen, onclose, onerror, onmessage, backOff, closeWebSocket])
|
||||
|
||||
useEffect(() => {
|
||||
if(!url) {
|
||||
return;
|
||||
}
|
||||
if(!url) return;
|
||||
if(webSocket !== undefined) return;
|
||||
if(isBackingOff) return;
|
||||
console.debug("[useWebSocket] Hook mounted, opening connection as soon as possible...")
|
||||
// @ts-ignore
|
||||
const handle = openWebSocketAfterBackoff()
|
||||
return () => {
|
||||
clearTimeout(handle)
|
||||
}
|
||||
}, [url, openWebSocketAfterBackoff])
|
||||
const openWebSocketAfterBackoff = backOff(openWebSocket);
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
openWebSocketAfterBackoff()
|
||||
}, [url, isBackingOff, webSocketState])
|
||||
|
||||
return {webSocket, webSocketState, openWebSocket, closeWebSocket}
|
||||
return {webSocket, webSocketState, webSocketBackoffMs}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue