From 25d51aff3fb9b3bbb271ff22de783bed7a4e98e3 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 16:31:50 +0000 Subject: [PATCH] Enable + make App.tsx renderer init idempotent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App.tsx's prepare() useEffect ran four .init() calls (SessionDataManager, RoomSessionManager, RoomEngine, Communication) without any guard, plus an immediate heartbeat ping and a legacy authentication track. Under StrictMode dev double-invoke, those fire twice — risking duplicate session/communication state. - Gate the four .init() chain behind gameInitPromiseRef: both the first and the simulated second invocation await the same promise. - Gate the legacy track + immediate heartbeat behind bootstrapDoneRef. - Heartbeat and remember-rotate intervals were already idempotent (clearInterval before setInterval); ticker registration was already guarded by tickersStartedRef; renderer/warmup were already gated by rendererPromiseRef/warmupPromiseRef. No change needed there. Wrap in in src/index.tsx now that the renderer init path is double-invoke safe. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q --- src/App.tsx | 26 ++++++++++++++++++++------ src/index.tsx | 26 ++++++++++++++------------ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4a04744..1e7a35f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -74,6 +74,8 @@ export const App: FC<{}> = props => const [ prepareTrigger, setPrepareTrigger ] = useState(0); const warmupPromiseRef = useRef>(null); const rendererPromiseRef = useRef>(null); + const gameInitPromiseRef = useRef | null>(null); + const bootstrapDoneRef = useRef(false); const tickersStartedRef = useRef(false); const heartbeatIntervalRef = useRef(null); const rememberRotateIntervalRef = useRef(null); @@ -385,14 +387,26 @@ export const App: FC<{}> = props => const renderer = await startRenderer(width, height); await startWarmup(width, height); - await GetSessionDataManager().init(); - await GetRoomSessionManager().init(); - await GetRoomEngine().init(); - await GetCommunication().init(); - if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'authentication', 'authok', []); + if(!gameInitPromiseRef.current) + { + gameInitPromiseRef.current = (async () => + { + await GetSessionDataManager().init(); + await GetRoomSessionManager().init(); + await GetRoomEngine().init(); + await GetCommunication().init(); + })(); + } - HabboWebTools.sendHeartBeat(); + await gameInitPromiseRef.current; + + if(!bootstrapDoneRef.current) + { + bootstrapDoneRef.current = true; + if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'authentication', 'authok', []); + HabboWebTools.sendHeartBeat(); + } if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current); heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000); diff --git a/src/index.tsx b/src/index.tsx index 079795c..40553d6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import { Suspense } from 'react'; +import { StrictMode, Suspense } from 'react'; import { createRoot } from 'react-dom/client'; import { ErrorBoundary } from 'react-error-boundary'; import { App } from './App'; @@ -44,15 +44,17 @@ import './css/toolbar/ToolBar.css'; import './css/widgets/FurnitureWidgets.css'; createRoot(document.getElementById('root')).render( - ( - - ) }> - }> - - - + + ( + + ) }> + }> + + + + );