import { Box, Button, config, Dialog, Text } from 'folds';
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient, SyncState } from 'matrix-js-sdk';
import React, { ReactNode, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
clearLocalSessionAndReload,
initClient,
startClient,
} from '../../../client/initMatrix';
import { ServerConfigsLoader } from '../../components/ServerConfigsLoader';
import { CapabilitiesProvider } from '../../hooks/useCapabilities';
import { MediaConfigProvider } from '../../hooks/useMediaConfig';
import { MatrixClientProvider } from '../../hooks/useMatrixClient';
import { SpecVersions } from './SpecVersions';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useSyncState } from '../../hooks/useSyncState';
import { SyncIndicator } from './SyncIndicator';
import { AuthMetadataProvider } from '../../hooks/useAuthMetadata';
import { getFallbackSession } from '../../state/sessions';
import { AutoDiscovery } from './AutoDiscovery';
import { AuthSplashScreen } from '../auth/AuthSplashScreen';
import { clearSessionBridge, writeSessionBridge } from '../../utils/sessionBridge';
function ClientRootLoading() {
return ;
}
const useLogoutListener = (mx?: MatrixClient) => {
useEffect(() => {
const handleLogout: HttpApiEventHandlerMap[HttpApiEvent.SessionLoggedOut] = async () => {
// Wipe the native session bridge before the reload — otherwise the dead
// access_token lingers in shared_prefs and CallDeclineReceiver spends
// the next login cycle posting 401s until writeSessionBridge overwrites.
await clearSessionBridge();
mx?.stopClient();
await mx?.clearStores();
window.localStorage.clear();
window.location.reload();
};
mx?.on(HttpApiEvent.SessionLoggedOut, handleLogout);
return () => {
mx?.removeListener(HttpApiEvent.SessionLoggedOut, handleLogout);
};
}, [mx]);
};
type ClientRootProps = {
children: ReactNode;
};
export function ClientRoot({ children }: ClientRootProps) {
const { t } = useTranslation();
const [loading, setLoading] = useState(true);
// Latches when the SDK reports SyncState.Error before the first PREPARED
// arrives — at that point matrix-js-sdk has already burned through its 3-try
// keep-alive backoff (~15-30s), so it's a definitive "I gave up" signal, not
// a transient blip. Cleared on Syncing in case the network recovers and the
// SDK pulls itself out before PREPARED.
const [syncErrored, setSyncErrored] = useState(false);
const { baseUrl, userId } = getFallbackSession() ?? {};
const [loadState, loadMatrix] = useAsyncCallback(
useCallback(() => {
const session = getFallbackSession();
if (!session) {
throw new Error('No session Found!');
}
return initClient(session);
}, [])
);
const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;
const [startState, startMatrix] = useAsyncCallback(
useCallback((m) => startClient(m), [])
);
useLogoutListener(mx);
useEffect(() => {
if (loadState.status === AsyncStatus.Idle) {
loadMatrix();
}
}, [loadState, loadMatrix]);
useEffect(() => {
if (mx && !mx.clientRunning) {
startMatrix(mx);
}
}, [mx, startMatrix]);
// Mirror {accessToken, baseUrl, userId} into native SharedPreferences so
// CallDeclineReceiver can send m.call.decline without booting the WebView.
// No-op on web.
useEffect(() => {
if (!mx) return;
writeSessionBridge(mx);
}, [mx]);
// When the OS reports the network is back, prod the sync loop instead of
// waiting for matrix-js-sdk's internal keep-alive jitter (5–10s backoff
// per `sync.js`). Only acts when the SDK is genuinely paused on a failed
// connection — Error or Reconnecting — so a healthy Syncing client just
// ignores the event. `retryImmediately` is itself idempotent, so spurious
// duplicate `online` events from flaky NICs are harmless.
useEffect(() => {
if (!mx) return undefined;
const onOnline = () => {
const state = mx.getSyncState();
if (state === SyncState.Error || state === SyncState.Reconnecting) {
mx.retryImmediately();
}
};
window.addEventListener('online', onOnline);
return () => window.removeEventListener('online', onOnline);
}, [mx]);
useSyncState(
mx,
useCallback((state) => {
if (state === SyncState.Prepared) {
setLoading(false);
setSyncErrored(false);
} else if (state === SyncState.Error) {
setSyncErrored(true);
} else if (state === SyncState.Syncing) {
setSyncErrored(false);
}
}, [])
);
// Sync-error case: SDK is wired up correctly, just stuck — poke the next
// /sync. Init/start failures need to re-run the failed step from scratch.
let onRetry: () => void;
if (loading && syncErrored && mx) {
onRetry = () => mx.retryImmediately();
} else if (mx) {
onRetry = () => startMatrix(mx);
} else {
onRetry = loadMatrix;
}
return (
{mx && }
{(loadState.status === AsyncStatus.Error ||
startState.status === AsyncStatus.Error ||
(loading && syncErrored)) && (
)}
{loading || !mx ? (
) : (
{(serverConfigs) => (
{children}
)}
)}
);
}