Complete Telegram bot login from callback URL
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m18s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m18s
This commit is contained in:
@@ -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>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user