mirror of
https://github.com/Steffo99/todocolors.git
synced 2024-11-25 17:54: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 {CreateBoardChapter} from "@/app/[lang]/(page)/CreateBoardChapter"
|
||||||
import {ExistingBoardChapter} from "@/app/[lang]/(page)/ExistingBoardChapter"
|
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"
|
import {default as React} from "react"
|
||||||
|
|
||||||
|
|
||||||
export async function RootMain({lng}: {lng: string}) {
|
export async function RootMain({lng}: {lng: string}) {
|
||||||
return (
|
return (
|
||||||
<main className={style.pageMain}>
|
<main className={style.rootMain}>
|
||||||
<CreateBoardChapter lng={lng}/>
|
<CreateBoardChapter lng={lng}/>
|
||||||
<ExistingBoardChapter lng={lng}/>
|
<ExistingBoardChapter lng={lng}/>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
|
||||||
|
|
||||||
export function BoardMain({className}: {className?: string}) {
|
export function BoardMain({className}: {className?: string}) {
|
||||||
const {webSocketState} = useManagedBoard()
|
const {webSocketState, webSocketBackoffMs} = useManagedBoard()
|
||||||
|
|
||||||
switch(webSocketState) {
|
switch(webSocketState) {
|
||||||
case undefined:
|
case undefined:
|
||||||
|
@ -18,6 +18,6 @@ export function BoardMain({className}: {className?: string}) {
|
||||||
case WebSocket.CLOSING:
|
case WebSocket.CLOSING:
|
||||||
return <BoardMainIcon icon={<FontAwesomeIcon size={"4x"} icon={faLinkSlash} beatFade/>} text={"Disconnessione..."} className={className}/>
|
return <BoardMainIcon icon={<FontAwesomeIcon size={"4x"} icon={faLinkSlash} beatFade/>} text={"Disconnessione..."} className={className}/>
|
||||||
case WebSocket.CLOSED:
|
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},
|
tasksById: {[id: string]: Task},
|
||||||
taskGroups: TaskGroup[],
|
taskGroups: TaskGroup[],
|
||||||
webSocketState: number | undefined,
|
webSocketState: number | undefined,
|
||||||
|
webSocketBackoffMs: number,
|
||||||
isEditingTitle: boolean,
|
isEditingTitle: boolean,
|
||||||
stopEditingTitle: () => void,
|
stopEditingTitle: () => void,
|
||||||
startEditingTitle: () => void,
|
startEditingTitle: () => void,
|
||||||
|
@ -38,7 +39,7 @@ export interface UseBoardReturns {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBoard(name: string): 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: [taskGrouper, groupSorter, groupNamer], move: moveGrouper, next: nextGrouper, previous: previousGrouper} = useCycleState(TASK_GROUPERS);
|
||||||
const {value: taskSorter, move: moveSorter, next: nextSorter, previous: previousSorter} = useCycleState(TASK_SORTERS);
|
const {value: taskSorter, move: moveSorter, next: nextSorter, previous: previousSorter} = useCycleState(TASK_SORTERS);
|
||||||
|
@ -56,6 +57,7 @@ export function useBoard(name: string): UseBoardReturns {
|
||||||
tasksById,
|
tasksById,
|
||||||
taskGroups,
|
taskGroups,
|
||||||
webSocketState,
|
webSocketState,
|
||||||
|
webSocketBackoffMs,
|
||||||
isEditingTitle,
|
isEditingTitle,
|
||||||
stopEditingTitle,
|
stopEditingTitle,
|
||||||
startEditingTitle,
|
startEditingTitle,
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function useBoardWs(name: string) {
|
||||||
|
|
||||||
const {state, act} = useBoardState();
|
const {state, act} = useBoardState();
|
||||||
|
|
||||||
const {webSocket, webSocketState} = useWs(wsFullURL, {
|
const {webSocket, webSocketState, webSocketBackoffMs} = useWs(wsFullURL, {
|
||||||
onopen: useCallback(({}) => {
|
onopen: useCallback(({}) => {
|
||||||
console.debug("[useBoardWs] Connected to board:", name);
|
console.debug("[useBoardWs] Connected to board:", name);
|
||||||
act(null);
|
act(null);
|
||||||
|
@ -27,9 +27,8 @@ export function useBoardWs(name: string) {
|
||||||
console.error("[useBoardWs] Encountered a WebSocket error, closing current connection:", event);
|
console.error("[useBoardWs] Encountered a WebSocket error, closing current connection:", event);
|
||||||
closeWebSocket()
|
closeWebSocket()
|
||||||
}, []),
|
}, []),
|
||||||
onclose: useCallback(({event, openWebSocket}: WebSocketHandlerParams<Event>) => {
|
onclose: useCallback(({event}: WebSocketHandlerParams<Event>) => {
|
||||||
console.debug("[useBoardWs] WebSocket was closed, trying to reconnect:", event);
|
console.debug("[useBoardWs] WebSocket was closed:", event);
|
||||||
openWebSocket()
|
|
||||||
}, [])
|
}, [])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,5 +41,5 @@ export function useBoardWs(name: string) {
|
||||||
webSocket.send(JSON.stringify(data));
|
webSocket.send(JSON.stringify(data));
|
||||||
}, [webSocket, webSocketState])
|
}, [webSocket, webSocketState])
|
||||||
|
|
||||||
return {state, sendAction, webSocketState}
|
return {state, sendAction, webSocketState, webSocketBackoffMs}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
import {Body} from "@/app/[lang]/Body"
|
import {Body} from "@/app/[lang]/(layout)/Body"
|
||||||
import {StarredManager} from "@/app/[lang]/StarContext"
|
import {StarredManager} from "@/app/[lang]/StarContext"
|
||||||
import type {Metadata as NextMetadata} from "next"
|
import type {Metadata as NextMetadata} from "next"
|
||||||
import {default as React, ReactNode} from "react"
|
import {default as React, ReactNode} from "react"
|
||||||
|
|
|
@ -9,11 +9,3 @@
|
||||||
|
|
||||||
padding: 8px;
|
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> {
|
export interface WebSocketHandlerParams<E extends Event> {
|
||||||
event: E,
|
event: E,
|
||||||
openWebSocket: () => void,
|
openWebSocketAfterBackoff: () => void,
|
||||||
closeWebSocket: () => void,
|
closeWebSocket: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ export interface WebSocketHandlers {
|
||||||
export function useWs(url: string | undefined, {onclose, onerror, onmessage, onopen}: WebSocketHandlers) {
|
export function useWs(url: string | undefined, {onclose, onerror, onmessage, onopen}: WebSocketHandlers) {
|
||||||
const [webSocket, setWebSocket] = useState<WebSocket | undefined>(undefined)
|
const [webSocket, setWebSocket] = useState<WebSocket | undefined>(undefined)
|
||||||
const [webSocketState, setWebSocketState] = useState<number | undefined>(undefined);
|
const [webSocketState, setWebSocketState] = useState<number | undefined>(undefined);
|
||||||
|
const [isBackingOff, setBackingOff] = useState<boolean>(false);
|
||||||
const [webSocketBackoffMs, setWebSocketBackoffMs] = useState<number>(1);
|
const [webSocketBackoffMs, setWebSocketBackoffMs] = useState<number>(1);
|
||||||
|
|
||||||
const closeWebSocket = useCallback(() => {
|
const closeWebSocket = useCallback(() => {
|
||||||
|
@ -36,56 +37,57 @@ export function useWs(url: string | undefined, {onclose, onerror, onmessage, ono
|
||||||
setWebSocketState(WebSocket.CLOSED);
|
setWebSocketState(WebSocket.CLOSED);
|
||||||
}, [webSocket])
|
}, [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(() => {
|
const openWebSocket = useCallback(() => {
|
||||||
console.debug("[useWebSocket] Opening WebSocket:", url);
|
console.debug("[useWebSocket] Opening WebSocket:", url);
|
||||||
if(url === undefined) {
|
if(url === undefined) {
|
||||||
console.warn("[useWebSocket] Trying to open WebSocket, but no URL has been given; ignoring request...")
|
console.warn("[useWebSocket] Trying to open WebSocket, but no URL has been given; ignoring request...")
|
||||||
return;
|
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
|
setWebSocketState(WebSocket.CONNECTING); // Workaround for constructor blocking and giving no feedback to the user
|
||||||
const sock = new WebSocket(url);
|
const sock = new WebSocket(url);
|
||||||
|
const openWebSocketAfterBackoff = backOff(openWebSocket);
|
||||||
sock.onopen = (event) => {
|
sock.onopen = (event) => {
|
||||||
console.debug("[useWebSocket] Opened connection:", event)
|
console.debug("[useWebSocket] Opened connection:", event)
|
||||||
setWebSocket(sock)
|
setWebSocket(sock)
|
||||||
setWebSocketState(sock.readyState)
|
setWebSocketState(sock.readyState)
|
||||||
setWebSocketBackoffMs(1)
|
onopen?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||||
onopen?.({event, openWebSocket, closeWebSocket});
|
|
||||||
}
|
}
|
||||||
sock.onclose = (event) => {
|
sock.onclose = (event) => {
|
||||||
console.debug("[useWebSocket] Closed connection:", event)
|
console.debug("[useWebSocket] Closed connection:", event)
|
||||||
setWebSocket(undefined)
|
setWebSocket(undefined)
|
||||||
setWebSocketState(sock.readyState);
|
setWebSocketState(sock.readyState);
|
||||||
onclose?.({event, openWebSocket, closeWebSocket});
|
onclose?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||||
}
|
}
|
||||||
sock.onerror = (event) => {
|
sock.onerror = (event) => {
|
||||||
console.error("[useWebSocket] Error in connection:", event)
|
console.error("[useWebSocket] Error in connection:", event)
|
||||||
setWebSocketState(sock.readyState)
|
setWebSocketState(sock.readyState)
|
||||||
setWebSocketBackoffMs(prev => prev * 2)
|
onerror?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||||
onerror?.({event, openWebSocket, closeWebSocket});
|
|
||||||
}
|
}
|
||||||
sock.onmessage = (event) => {
|
sock.onmessage = (event) => {
|
||||||
console.debug("[useWebSocket] Received message:", event)
|
console.debug("[useWebSocket] Received message:", event)
|
||||||
onmessage?.({event, openWebSocket, closeWebSocket});
|
onmessage?.({event, openWebSocketAfterBackoff, closeWebSocket});
|
||||||
}
|
}
|
||||||
}, [url, onopen, onclose, onerror, onmessage])
|
}, [url, onopen, onclose, onerror, onmessage, backOff, closeWebSocket])
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const openWebSocketAfterBackoff = useCallback(() => {
|
|
||||||
console.debug("[useWebSocket] Backing off for:", webSocketBackoffMs, "ms")
|
|
||||||
return setTimeout(openWebSocket, webSocketBackoffMs)
|
|
||||||
}, [openWebSocket, webSocketBackoffMs])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!url) {
|
if(!url) return;
|
||||||
return;
|
if(webSocket !== undefined) return;
|
||||||
}
|
if(isBackingOff) return;
|
||||||
console.debug("[useWebSocket] Hook mounted, opening connection as soon as possible...")
|
console.debug("[useWebSocket] Hook mounted, opening connection as soon as possible...")
|
||||||
// @ts-ignore
|
const openWebSocketAfterBackoff = backOff(openWebSocket);
|
||||||
const handle = openWebSocketAfterBackoff()
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
return () => {
|
openWebSocketAfterBackoff()
|
||||||
clearTimeout(handle)
|
}, [url, isBackingOff, webSocketState])
|
||||||
}
|
|
||||||
}, [url, openWebSocketAfterBackoff])
|
|
||||||
|
|
||||||
return {webSocket, webSocketState, openWebSocket, closeWebSocket}
|
return {webSocket, webSocketState, webSocketBackoffMs}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue