websocket automatic reconnection

This commit is contained in:
otsmr 2025-01-21 08:41:27 +01:00
parent e358cf2e57
commit aa608be1e4
5 changed files with 57 additions and 18 deletions

3
devtools_options.yaml Normal file
View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -12,6 +12,7 @@ import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server; import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
import 'package:twonly/src/signal/signal_helper.dart'; import 'package:twonly/src/signal/signal_helper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
class Result<T, E> { class Result<T, E> {
@ -31,19 +32,19 @@ class ApiProvider {
final String apiUrl; final String apiUrl;
final String? backupApiUrl; final String? backupApiUrl;
final log = Logger("connect::ApiProvider"); final log = Logger("connect::ApiProvider");
final HashMap<String, List<Function>> _callbacks = HashMap(); Function(bool)? _connectionStateCallback;
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap(); final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
WebSocketChannel? _channel; IOWebSocketChannel? _channel;
Future<bool> _connectTo(String apiUrl) async { Future<bool> _connectTo(String apiUrl) async {
try { try {
var channel = WebSocketChannel.connect( var channel = IOWebSocketChannel.connect(
Uri.parse(apiUrl), Uri.parse(apiUrl),
); );
_channel = channel; _channel = channel;
_channel!.stream.listen(_onData); _channel!.stream.listen(_onData, onDone: _onDone, onError: _onError);
await _channel!.ready; await _channel!.ready;
log.info("Websocket is connected!"); log.info("Websocket is connected!");
print("Websocket is connected!"); print("Websocket is connected!");
@ -56,9 +57,14 @@ class ApiProvider {
Future<bool> connect(Function(bool)? callBack) async { Future<bool> connect(Function(bool)? callBack) async {
print("Trying to connect to the backend $apiUrl!"); print("Trying to connect to the backend $apiUrl!");
if (callBack != null) {
_connectionStateCallback = callBack;
}
if (_channel != null && _channel!.closeCode != null) { if (_channel != null && _channel!.closeCode != null) {
print("is connected");
return true; return true;
} }
log.info("Trying to connect to the backend $apiUrl!"); log.info("Trying to connect to the backend $apiUrl!");
if (await _connectTo(apiUrl)) { if (await _connectTo(apiUrl)) {
if (callBack != null) callBack(true); if (callBack != null) callBack(true);
@ -76,6 +82,37 @@ class ApiProvider {
bool get isConnected => _channel != null && _channel!.closeCode != null; bool get isConnected => _channel != null && _channel!.closeCode != null;
void _onDone() {
if (_connectionStateCallback != null) {
_connectionStateCallback!(false);
}
_channel = null;
tryToReconnect(5);
}
void _onError(dynamic e) {
if (_connectionStateCallback != null) {
_connectionStateCallback!(false);
}
_channel = null;
tryToReconnect(5);
}
void tryToReconnect(int delay) {
Future.delayed(Duration(seconds: delay)).then(
(value) async {
if (!await connect(_connectionStateCallback)) {
if (delay > 60 * 5) {
delay = 60 * 5;
} else {
delay = delay * 2;
}
tryToReconnect(delay);
}
},
);
}
void _onData(dynamic msgBuffer) { void _onData(dynamic msgBuffer) {
try { try {
final msg = server.ServerToClient.fromBuffer(msgBuffer); final msg = server.ServerToClient.fromBuffer(msgBuffer);
@ -89,13 +126,6 @@ class ApiProvider {
} }
} }
void addNotifier(String messageType, Function callBackFunction) {
if (!_callbacks.containsKey(messageType)) {
_callbacks[messageType] = [];
}
_callbacks[messageType]!.add(callBackFunction);
}
// TODO: There must be a smarter move to do that :/ // TODO: There must be a smarter move to do that :/
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async { Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
final startTime = DateTime.now(); final startTime = DateTime.now();

View file

@ -25,14 +25,17 @@ Future<bool> isUserCreated() async {
Future<UserData?> getUser() async { Future<UserData?> getUser() async {
final storage = getSecureStorage(); final storage = getSecureStorage();
String? userJson = await storage.read(key: "user_data"); String? userJson = await storage.read(key: "user_data");
print(userJson);
if (userJson == null) { if (userJson == null) {
return null; return null;
} }
try { try {
final userMap = jsonDecode(userJson) as Map<String, dynamic>; final userMap = jsonDecode(userJson) as Map<String, dynamic>;
final user = UserData.fromJson(userMap); final user = UserData.fromJson(userMap);
print(user);
return user; return user;
} catch (_) { } catch (e) {
print(e);
return null; return null;
} }
} }

View file

@ -43,10 +43,10 @@ class CameraPreviewViewState extends State<CameraPreviewView> {
final size = Size(50, 300); final size = Size(50, 300);
_controller.initialize().then((_) { // _controller.initialize().then((_) {
_controller.value = _controller.value.copyWith(previewSize: size); // _controller.value = _controller.value.copyWith(previewSize: size);
setState(() {}); // setState(() {});
}); // });
// Next, initialize the controller. This returns a Future. // Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize(); _initializeControllerFuture = _controller.initialize();

View file

@ -1,3 +1,5 @@
import 'package:twonly/src/model/user_data_json.dart';
import '../settings/settings_controller.dart'; import '../settings/settings_controller.dart';
import '../settings/settings_view.dart'; import '../settings/settings_view.dart';
import '../utils.dart'; import '../utils.dart';
@ -14,13 +16,14 @@ class ProfileView extends StatefulWidget {
} }
class _ProfileViewState extends State<ProfileView> { class _ProfileViewState extends State<ProfileView> {
final Future<UserData?> _userData = getUser();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// var user = await getUser();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: FutureBuilder( title: FutureBuilder(
future: getUser(), future: _userData,
builder: (context, snap) { builder: (context, snap) {
if (snap.hasData) { if (snap.hasData) {
return Text("Hello ${snap.data!.username}!"); return Text("Hello ${snap.data!.username}!");