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(
|
Future<TelegramBotLoginSession> completeTelegramBotLogin(String token) async {
|
||||||
String token,
|
|
||||||
) async {
|
|
||||||
final data = await _graphql(
|
final data = await _graphql(
|
||||||
'''
|
'''
|
||||||
query TelegramBotLoginStatus(\$token: String!) {
|
mutation CompleteTelegramBotLogin(\$token: String!) {
|
||||||
telegramBotLoginStatus(token: \$token) {
|
completeTelegramBotLogin(token: \$token) {
|
||||||
status
|
|
||||||
sessionToken
|
sessionToken
|
||||||
user {
|
user {
|
||||||
id
|
id
|
||||||
@@ -149,8 +146,8 @@ class MapflowApi {
|
|||||||
''',
|
''',
|
||||||
variables: {'token': token},
|
variables: {'token': token},
|
||||||
);
|
);
|
||||||
return TelegramBotLoginStatus.fromJson(
|
return TelegramBotLoginSession.fromJson(
|
||||||
data['telegramBotLoginStatus'] as Map<String, dynamic>,
|
data['completeTelegramBotLogin'] as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,12 @@ String telegramLoginData() => '';
|
|||||||
|
|
||||||
String mapflowSessionToken() => '';
|
String mapflowSessionToken() => '';
|
||||||
|
|
||||||
String pendingTelegramLoginToken() => '';
|
|
||||||
|
|
||||||
String telegramLoginTokenFromUrl() => '';
|
String telegramLoginTokenFromUrl() => '';
|
||||||
|
|
||||||
void saveMapflowSessionToken(String token) {}
|
void saveMapflowSessionToken(String token) {}
|
||||||
|
|
||||||
void clearMapflowSession() {}
|
void clearMapflowSession() {}
|
||||||
|
|
||||||
void savePendingTelegramLoginToken(String token) {}
|
|
||||||
|
|
||||||
void clearPendingTelegramLoginToken() {}
|
|
||||||
|
|
||||||
void openExternalUrl(String url) {}
|
void openExternalUrl(String url) {}
|
||||||
|
|
||||||
void reloadApp() {}
|
void reloadApp() {}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:web/web.dart' as web;
|
|||||||
|
|
||||||
const telegramLoginStorageKey = 'mapflow.telegramLoginData';
|
const telegramLoginStorageKey = 'mapflow.telegramLoginData';
|
||||||
const mapflowSessionStorageKey = 'mapflow.sessionToken';
|
const mapflowSessionStorageKey = 'mapflow.sessionToken';
|
||||||
const pendingTelegramLoginStorageKey = 'mapflow.pendingTelegramLoginToken';
|
|
||||||
|
|
||||||
@JS('window.Telegram.WebApp.initData')
|
@JS('window.Telegram.WebApp.initData')
|
||||||
external JSString? get _telegramInitData;
|
external JSString? get _telegramInitData;
|
||||||
@@ -20,9 +19,6 @@ String telegramLoginData() =>
|
|||||||
String mapflowSessionToken() =>
|
String mapflowSessionToken() =>
|
||||||
web.window.localStorage.getItem(mapflowSessionStorageKey) ?? '';
|
web.window.localStorage.getItem(mapflowSessionStorageKey) ?? '';
|
||||||
|
|
||||||
String pendingTelegramLoginToken() =>
|
|
||||||
web.window.localStorage.getItem(pendingTelegramLoginStorageKey) ?? '';
|
|
||||||
|
|
||||||
String telegramLoginTokenFromUrl() {
|
String telegramLoginTokenFromUrl() {
|
||||||
final uri = Uri.parse(web.window.location.href);
|
final uri = Uri.parse(web.window.location.href);
|
||||||
return uri.queryParameters['telegram_login'] ?? '';
|
return uri.queryParameters['telegram_login'] ?? '';
|
||||||
@@ -40,19 +36,10 @@ void saveMapflowSessionToken(String token) {
|
|||||||
void clearMapflowSession() {
|
void clearMapflowSession() {
|
||||||
web.window.localStorage.removeItem(mapflowSessionStorageKey);
|
web.window.localStorage.removeItem(mapflowSessionStorageKey);
|
||||||
web.window.localStorage.removeItem(telegramLoginStorageKey);
|
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) {
|
void openExternalUrl(String url) {
|
||||||
web.window.open(url, '_blank', 'noopener,noreferrer');
|
web.window.location.assign(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadApp() {
|
void reloadApp() {
|
||||||
|
|||||||
@@ -153,26 +153,19 @@ class TelegramBotLogin {
|
|||||||
final DateTime expiresAt;
|
final DateTime expiresAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TelegramBotLoginStatus {
|
class TelegramBotLoginSession {
|
||||||
const TelegramBotLoginStatus({
|
const TelegramBotLoginSession({
|
||||||
required this.status,
|
|
||||||
required this.sessionToken,
|
required this.sessionToken,
|
||||||
required this.user,
|
required this.user,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory TelegramBotLoginStatus.fromJson(Map<String, dynamic> json) {
|
factory TelegramBotLoginSession.fromJson(Map<String, dynamic> json) {
|
||||||
final user = json['user'];
|
return TelegramBotLoginSession(
|
||||||
return TelegramBotLoginStatus(
|
sessionToken: json['sessionToken'] as String,
|
||||||
status: json['status'] as String,
|
user: AppUser.fromJson(json['user'] as Map<String, dynamic>),
|
||||||
sessionToken: json['sessionToken'] as String?,
|
|
||||||
user: user is Map<String, dynamic> ? AppUser.fromJson(user) : null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String status;
|
final String sessionToken;
|
||||||
final String? sessionToken;
|
final AppUser user;
|
||||||
final AppUser? user;
|
|
||||||
|
|
||||||
bool get isConfirmed => status == 'CONFIRMED' && sessionToken != null;
|
|
||||||
bool get isExpired => status == 'EXPIRED';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,6 @@ class _TelegramLoginScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
||||||
final _api = MapflowApi();
|
final _api = MapflowApi();
|
||||||
Timer? _pollTimer;
|
|
||||||
Timer? _countdownTimer;
|
Timer? _countdownTimer;
|
||||||
DateTime? _loginExpiresAt;
|
DateTime? _loginExpiresAt;
|
||||||
var _loading = false;
|
var _loading = false;
|
||||||
@@ -307,22 +306,13 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final urlToken = telegram_session.telegramLoginTokenFromUrl();
|
final urlToken = telegram_session.telegramLoginTokenFromUrl();
|
||||||
final pendingToken = urlToken.isNotEmpty
|
if (urlToken.isNotEmpty) {
|
||||||
? urlToken
|
_completeLogin(urlToken);
|
||||||
: telegram_session.pendingTelegramLoginToken();
|
|
||||||
if (pendingToken.isNotEmpty) {
|
|
||||||
telegram_session.savePendingTelegramLoginToken(pendingToken);
|
|
||||||
_pollLogin(pendingToken);
|
|
||||||
_pollTimer = Timer.periodic(
|
|
||||||
const Duration(seconds: 2),
|
|
||||||
(_) => _pollLogin(pendingToken),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pollTimer?.cancel();
|
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -333,14 +323,8 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
|||||||
_message = '';
|
_message = '';
|
||||||
});
|
});
|
||||||
final login = await _api.startTelegramBotLogin();
|
final login = await _api.startTelegramBotLogin();
|
||||||
telegram_session.savePendingTelegramLoginToken(login.token);
|
|
||||||
telegram_session.openExternalUrl(login.botUrl);
|
telegram_session.openExternalUrl(login.botUrl);
|
||||||
_pollTimer?.cancel();
|
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
_pollTimer = Timer.periodic(
|
|
||||||
const Duration(seconds: 2),
|
|
||||||
(_) => _pollLogin(login.token),
|
|
||||||
);
|
|
||||||
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -353,28 +337,14 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pollLogin(String token) async {
|
Future<void> _completeLogin(String token) async {
|
||||||
final status = await _api.fetchTelegramBotLoginStatus(token);
|
setState(() {
|
||||||
if (status.isExpired) {
|
_loading = true;
|
||||||
_pollTimer?.cancel();
|
_message = '';
|
||||||
|
});
|
||||||
|
final session = await _api.completeTelegramBotLogin(token);
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
telegram_session.clearPendingTelegramLoginToken();
|
telegram_session.saveMapflowSessionToken(session.sessionToken);
|
||||||
if (!mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_loginExpiresAt = null;
|
|
||||||
setState(() => _message = 'Ссылка устарела. Запусти вход заново.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!status.isConfirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pollTimer?.cancel();
|
|
||||||
_countdownTimer?.cancel();
|
|
||||||
telegram_session.saveMapflowSessionToken(status.sessionToken!);
|
|
||||||
telegram_session.clearPendingTelegramLoginToken();
|
|
||||||
widget.onAuthenticated();
|
widget.onAuthenticated();
|
||||||
telegram_session.reloadApp();
|
telegram_session.reloadApp();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user