Refactor Telegram authentication flow to prevent interference during QR login and DC migration

- Introduced flags to track the QR flow and DC migration states, preventing polling and authentication checks during sensitive operations.
- Updated polling intervals and added initial delays to enhance the user experience during authentication.
- Improved code clarity with detailed comments explaining the new flow control mechanisms.

This update ensures a smoother and more reliable authentication process for users, reducing the risk of errors during critical operations.
This commit is contained in:
M3gA-Mind
2026-01-30 20:47:03 +05:30
parent bea890da8e
commit f327c27b30
3 changed files with 79 additions and 12 deletions
+26 -4
View File
@@ -325,7 +325,9 @@ const TelegramConnectionModal = ({
return () => clearInterval(interval);
}, [qrCodeExpires, currentStep, isAuthenticating, startQrCodeFlow]);
// Poll authentication status every 5 seconds when QR code is displayed
// Poll authentication status when QR code is displayed
// IMPORTANT: Polling is disabled during active QR flow to prevent interference
// with the signInWithQrCode authentication process and DC migration
useEffect(() => {
if (currentStep !== "qr" || !qrCodeUrl || isAuthenticating) {
return;
@@ -334,10 +336,25 @@ const TelegramConnectionModal = ({
const pollAuthStatus = async () => {
try {
if (!mtprotoService.isReady()) return;
// CRITICAL: Don't poll if QR flow or DC migration is in progress
// This prevents the infinite reconnection loop caused by concurrent
// calls to checkAuthorization() during sensitive operations
if (mtprotoService.shouldBlockExternalCalls()) {
console.debug("Skipping auth poll - QR flow or DC migration in progress");
return;
}
const client = mtprotoService.getClient();
const isAuthorized = await client.checkAuthorization();
if (isAuthorized) {
// Double-check we're still not in a sensitive state after the async call
if (mtprotoService.shouldBlockExternalCalls()) {
console.debug("Skipping auth completion - flow state changed");
return;
}
try {
const me = await client.getMe();
if (!user) {
@@ -383,10 +400,15 @@ const TelegramConnectionModal = ({
}
};
pollAuthStatus();
const interval = setInterval(pollAuthStatus, 5000);
// Initial poll after a delay to allow QR flow to start
const initialTimeout = setTimeout(pollAuthStatus, 2000);
// Poll every 10 seconds (increased from 5s to reduce interference)
const interval = setInterval(pollAuthStatus, 10000);
return () => clearInterval(interval);
return () => {
clearTimeout(initialTimeout);
clearInterval(interval);
};
}, [
currentStep,
qrCodeUrl,
+44 -8
View File
@@ -34,6 +34,10 @@ class MTProtoService {
// QR login race condition guard
private isScanningComplete = false;
// More reliable flag that tracks the entire QR flow lifecycle
private isQrFlowInProgress = false;
// DC migration tracking to prevent interference during migration
private isDcMigrating = false;
private constructor() {
// Private constructor to enforce singleton
@@ -203,8 +207,10 @@ class MTProtoService {
);
}
// Reset race condition guard
// Reset race condition guards and set flow in progress
this.isScanningComplete = false;
this.isQrFlowInProgress = true;
this.isDcMigrating = false;
try {
const user = await this.client.signInUserWithQrCode(
@@ -243,11 +249,16 @@ class MTProtoService {
const errorMessage = err.message || "";
// DC migration — the library handles this internally but we
// notify the UI for status display
if (errorMessage.includes("NETWORK_MIGRATE_")) {
const dcMatch = errorMessage.match(/NETWORK_MIGRATE_(\d+)/);
// notify the UI for status display and set migration flag
if (errorMessage.includes("NETWORK_MIGRATE_") || errorMessage.includes("MIGRATE_")) {
const dcMatch = errorMessage.match(/(?:NETWORK_)?MIGRATE_(\d+)/);
if (dcMatch) {
this.isDcMigrating = true;
onStatus?.({ type: "dc_migration", dcId: Number(dcMatch[1]) });
// Clear migration flag after a delay to allow migration to complete
setTimeout(() => {
this.isDcMigrating = false;
}, 10000);
}
// Don't stop — let the library handle DC migration
return false;
@@ -280,8 +291,10 @@ class MTProtoService {
},
);
// Mark scanning as complete
// Mark scanning as complete and clear flow flag
this.isScanningComplete = true;
this.isQrFlowInProgress = false;
this.isDcMigrating = false;
onStatus?.({ type: "scanning_complete" });
// Save session after successful login (critical after DC migration
@@ -297,6 +310,8 @@ class MTProtoService {
return user;
} catch (error) {
this.isScanningComplete = true;
this.isQrFlowInProgress = false;
this.isDcMigrating = false;
const errorMessage =
error instanceof Error ? error.message : String(error);
@@ -317,11 +332,28 @@ class MTProtoService {
}
/**
* Check if QR scanning is still in progress (not yet complete or errored).
* Use this to avoid starting a new QR flow while one is active.
* Check if QR login flow is in progress.
* Use this to prevent external operations (like polling) from interfering
* with the active authentication flow.
*/
isQrScanningActive(): boolean {
return !this.isScanningComplete;
return this.isQrFlowInProgress;
}
/**
* Check if DC migration is in progress.
* During DC migration, external calls to the client can corrupt state.
*/
isDcMigrationInProgress(): boolean {
return this.isDcMigrating;
}
/**
* Check if any sensitive operation is in progress that should block polling.
* This includes QR flow and DC migration.
*/
shouldBlockExternalCalls(): boolean {
return this.isQrFlowInProgress || this.isDcMigrating;
}
/**
@@ -449,6 +481,10 @@ class MTProtoService {
this.initializePromise = null;
this.connectPromise = null;
this.checkConnectionPromise = null;
// Reset QR flow flags
this.isScanningComplete = false;
this.isQrFlowInProgress = false;
this.isDcMigrating = false;
}
/**
+9
View File
@@ -55,6 +55,15 @@ export const checkAuthStatus = createAsyncThunk(
);
}
// CRITICAL: Don't check auth status if QR flow or DC migration is in progress
// This prevents interference with the active authentication flow
if (mtprotoService.shouldBlockExternalCalls()) {
console.debug("checkAuthStatus skipped - QR flow or DC migration in progress");
const state = getState() as RootState;
const u = state.telegram.byUser[userId];
return (u?.currentUser as TelegramUser) || null;
}
const now = Date.now();
if (isCheckingAuth && now - lastCheckTime < MIN_CHECK_INTERVAL) {
const state = getState() as RootState;