mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
Enable <StrictMode> + make App.tsx renderer init idempotent
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 <App /> in <StrictMode> in src/index.tsx now that the renderer init path is double-invoke safe. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
+20
-6
@@ -74,6 +74,8 @@ export const App: FC<{}> = props =>
|
|||||||
const [ prepareTrigger, setPrepareTrigger ] = useState(0);
|
const [ prepareTrigger, setPrepareTrigger ] = useState(0);
|
||||||
const warmupPromiseRef = useRef<Promise<void>>(null);
|
const warmupPromiseRef = useRef<Promise<void>>(null);
|
||||||
const rendererPromiseRef = useRef<Promise<any>>(null);
|
const rendererPromiseRef = useRef<Promise<any>>(null);
|
||||||
|
const gameInitPromiseRef = useRef<Promise<void> | null>(null);
|
||||||
|
const bootstrapDoneRef = useRef(false);
|
||||||
const tickersStartedRef = useRef(false);
|
const tickersStartedRef = useRef(false);
|
||||||
const heartbeatIntervalRef = useRef<number>(null);
|
const heartbeatIntervalRef = useRef<number>(null);
|
||||||
const rememberRotateIntervalRef = useRef<number>(null);
|
const rememberRotateIntervalRef = useRef<number>(null);
|
||||||
@@ -385,14 +387,26 @@ export const App: FC<{}> = props =>
|
|||||||
const renderer = await startRenderer(width, height);
|
const renderer = await startRenderer(width, height);
|
||||||
|
|
||||||
await startWarmup(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);
|
if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current);
|
||||||
heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000);
|
heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000);
|
||||||
|
|||||||
+14
-12
@@ -1,4 +1,4 @@
|
|||||||
import { Suspense } from 'react';
|
import { StrictMode, Suspense } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
@@ -44,15 +44,17 @@ import './css/toolbar/ToolBar.css';
|
|||||||
import './css/widgets/FurnitureWidgets.css';
|
import './css/widgets/FurnitureWidgets.css';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<ErrorBoundary
|
<StrictMode>
|
||||||
fallbackRender={ ({ error }) => (
|
<ErrorBoundary
|
||||||
<LoadingView
|
fallbackRender={ ({ error }) => (
|
||||||
isError={ true }
|
<LoadingView
|
||||||
message={ `Something went wrong.\n${ (error as Error)?.message ?? 'Unknown error' }` }
|
isError={ true }
|
||||||
homeUrl={ window.location.origin + '/' } />
|
message={ `Something went wrong.\n${ (error as Error)?.message ?? 'Unknown error' }` }
|
||||||
) }>
|
homeUrl={ window.location.origin + '/' } />
|
||||||
<Suspense fallback={ <LoadingView message="Loading…" /> }>
|
) }>
|
||||||
<App />
|
<Suspense fallback={ <LoadingView message="Loading…" /> }>
|
||||||
</Suspense>
|
<App />
|
||||||
</ErrorBoundary>
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user