Complete Telegram bot login from callback URL
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m18s

This commit is contained in:
Ruslan Bakiev
2026-05-13 19:36:09 +07:00
parent 5b2cd4158c
commit a8b6aa6e02
5 changed files with 23 additions and 82 deletions

View File

@@ -126,14 +126,11 @@ class MapflowApi {
);
}
Future<TelegramBotLoginStatus> fetchTelegramBotLoginStatus(
String token,
) async {
Future<TelegramBotLoginSession> completeTelegramBotLogin(String token) async {
final data = await _graphql(
'''
query TelegramBotLoginStatus(\$token: String!) {
telegramBotLoginStatus(token: \$token) {
status
mutation CompleteTelegramBotLogin(\$token: String!) {
completeTelegramBotLogin(token: \$token) {
sessionToken
user {
id
@@ -149,8 +146,8 @@ class MapflowApi {
''',
variables: {'token': token},
);
return TelegramBotLoginStatus.fromJson(
data['telegramBotLoginStatus'] as Map<String, dynamic>,
return TelegramBotLoginSession.fromJson(
data['completeTelegramBotLogin'] as Map<String, dynamic>,
);
}

View File

@@ -4,18 +4,12 @@ String telegramLoginData() => '';
String mapflowSessionToken() => '';
String pendingTelegramLoginToken() => '';
String telegramLoginTokenFromUrl() => '';
void saveMapflowSessionToken(String token) {}
void clearMapflowSession() {}
void savePendingTelegramLoginToken(String token) {}
void clearPendingTelegramLoginToken() {}
void openExternalUrl(String url) {}
void reloadApp() {}

View File

@@ -4,7 +4,6 @@ import 'package:web/web.dart' as web;
const telegramLoginStorageKey = 'mapflow.telegramLoginData';
const mapflowSessionStorageKey = 'mapflow.sessionToken';
const pendingTelegramLoginStorageKey = 'mapflow.pendingTelegramLoginToken';
@JS('window.Telegram.WebApp.initData')
external JSString? get _telegramInitData;
@@ -20,9 +19,6 @@ String telegramLoginData() =>
String mapflowSessionToken() =>
web.window.localStorage.getItem(mapflowSessionStorageKey) ?? '';
String pendingTelegramLoginToken() =>
web.window.localStorage.getItem(pendingTelegramLoginStorageKey) ?? '';
String telegramLoginTokenFromUrl() {
final uri = Uri.parse(web.window.location.href);
return uri.queryParameters['telegram_login'] ?? '';
@@ -40,19 +36,10 @@ void saveMapflowSessionToken(String token) {
void clearMapflowSession() {
web.window.localStorage.removeItem(mapflowSessionStorageKey);
web.window.localStorage.removeItem(telegramLoginStorageKey);
clearPendingTelegramLoginToken();
}
void savePendingTelegramLoginToken(String token) {
web.window.localStorage.setItem(pendingTelegramLoginStorageKey, token);
}
void clearPendingTelegramLoginToken() {
web.window.localStorage.removeItem(pendingTelegramLoginStorageKey);
}
void openExternalUrl(String url) {
web.window.open(url, '_blank', 'noopener,noreferrer');
web.window.location.assign(url);
}
void reloadApp() {

View File

@@ -153,26 +153,19 @@ class TelegramBotLogin {
final DateTime expiresAt;
}
class TelegramBotLoginStatus {
const TelegramBotLoginStatus({
required this.status,
class TelegramBotLoginSession {
const TelegramBotLoginSession({
required this.sessionToken,
required this.user,
});
factory TelegramBotLoginStatus.fromJson(Map<String, dynamic> json) {
final user = json['user'];
return TelegramBotLoginStatus(
status: json['status'] as String,
sessionToken: json['sessionToken'] as String?,
user: user is Map<String, dynamic> ? AppUser.fromJson(user) : null,
factory TelegramBotLoginSession.fromJson(Map<String, dynamic> json) {
return TelegramBotLoginSession(
sessionToken: json['sessionToken'] as String,
user: AppUser.fromJson(json['user'] as Map<String, dynamic>),
);
}
final String status;
final String? sessionToken;
final AppUser? user;
bool get isConfirmed => status == 'CONFIRMED' && sessionToken != null;
bool get isExpired => status == 'EXPIRED';
final String sessionToken;
final AppUser user;
}

View File

@@ -297,7 +297,6 @@ class _TelegramLoginScreen extends StatefulWidget {
class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
final _api = MapflowApi();
Timer? _pollTimer;
Timer? _countdownTimer;
DateTime? _loginExpiresAt;
var _loading = false;
@@ -307,22 +306,13 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
void initState() {
super.initState();
final urlToken = telegram_session.telegramLoginTokenFromUrl();
final pendingToken = urlToken.isNotEmpty
? urlToken
: telegram_session.pendingTelegramLoginToken();
if (pendingToken.isNotEmpty) {
telegram_session.savePendingTelegramLoginToken(pendingToken);
_pollLogin(pendingToken);
_pollTimer = Timer.periodic(
const Duration(seconds: 2),
(_) => _pollLogin(pendingToken),
);
if (urlToken.isNotEmpty) {
_completeLogin(urlToken);
}
}
@override
void dispose() {
_pollTimer?.cancel();
_countdownTimer?.cancel();
super.dispose();
}
@@ -333,14 +323,8 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
_message = '';
});
final login = await _api.startTelegramBotLogin();
telegram_session.savePendingTelegramLoginToken(login.token);
telegram_session.openExternalUrl(login.botUrl);
_pollTimer?.cancel();
_countdownTimer?.cancel();
_pollTimer = Timer.periodic(
const Duration(seconds: 2),
(_) => _pollLogin(login.token),
);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) {
if (mounted) {
setState(() {});
@@ -353,28 +337,14 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
});
}
Future<void> _pollLogin(String token) async {
final status = await _api.fetchTelegramBotLoginStatus(token);
if (status.isExpired) {
_pollTimer?.cancel();
_countdownTimer?.cancel();
telegram_session.clearPendingTelegramLoginToken();
if (!mounted) {
return;
}
_loginExpiresAt = null;
setState(() => _message = 'Ссылка устарела. Запусти вход заново.');
return;
}
if (!status.isConfirmed) {
return;
}
_pollTimer?.cancel();
Future<void> _completeLogin(String token) async {
setState(() {
_loading = true;
_message = '';
});
final session = await _api.completeTelegramBotLogin(token);
_countdownTimer?.cancel();
telegram_session.saveMapflowSessionToken(status.sessionToken!);
telegram_session.clearPendingTelegramLoginToken();
telegram_session.saveMapflowSessionToken(session.sessionToken);
widget.onAuthenticated();
telegram_session.reloadApp();
}