together_mobile/lib/models/websocket_model.dart

250 lines
6.8 KiB
Dart
Raw Normal View History

2023-08-10 19:08:46 +08:00
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
2023-08-10 19:08:46 +08:00
import 'package:hive_flutter/hive_flutter.dart';
2023-09-09 16:48:47 +08:00
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
2024-04-09 17:23:28 +08:00
import '/database/box_type.dart';
import '/utils/ws_receive_callback.dart';
2023-08-10 19:08:46 +08:00
2023-08-27 19:14:37 +08:00
enum SocketStatus {
connected,
reconnecting,
2023-08-27 19:14:37 +08:00
error,
closed,
2023-08-27 19:14:37 +08:00
}
// const chunkSize = 1024 * 1024;
2023-08-10 19:08:46 +08:00
class WebSocketManager extends ChangeNotifier {
late Uri wsUrl;
late WebSocketChannel channel;
String id = '';
2023-08-27 19:14:37 +08:00
SocketStatus socketStatus = SocketStatus.closed;
2023-08-10 19:08:46 +08:00
Timer? heartBeatTimer;
Timer? serverTimer;
Timer? reconnectTimer;
int reconnectCount = 5;
2023-08-10 19:08:46 +08:00
int reconnectTimes = 0;
Duration heartBeatTimeout = const Duration(seconds: 30);
Duration reconnectTimeout = const Duration(seconds: 5);
Map<String, Timer> sendImageTimer = {};
2023-08-10 19:08:46 +08:00
void connect(String userId, bool isReconnect) {
2023-08-10 19:08:46 +08:00
id = userId;
wsUrl = Uri.parse('ws://10.0.2.2:8000/ws/$id?is_reconnect=$isReconnect');
if (isReconnect) {
socketStatus = SocketStatus.reconnecting;
notifyListeners();
}
// This doesn't block the program whether it connect the server or not
// So heartBeat will be execute straightly
2023-08-10 19:08:46 +08:00
channel = WebSocketChannel.connect(wsUrl);
heartBeatInspect();
2023-08-10 19:08:46 +08:00
channel.stream.listen(onData, onError: onError, onDone: onDone);
}
2023-08-27 19:14:37 +08:00
void disconnect() {
channel.sink.close();
wsUrl = Uri();
id = '';
socketStatus = SocketStatus.closed;
notifyListeners();
2023-08-27 19:14:37 +08:00
heartBeatTimer?.cancel();
serverTimer?.cancel();
reconnectTimer?.cancel();
heartBeatTimer = null;
serverTimer = null;
reconnectTimer = null;
reconnectTimes = 0;
}
void onData(jsonData) async {
// If socket can receive msg, that means connection is estabilished
socketStatus = SocketStatus.connected;
notifyListeners();
print('websocket connected <$channel>');
if (reconnectTimer != null) {
reconnectTimer!.cancel();
reconnectTimer = null;
reconnectTimes = 0;
}
2023-08-10 19:08:46 +08:00
heartBeatInspect();
// Map<String, dynamic> data = json.decode(jsonData);
Map<String, dynamic> data =
await compute((message) => json.decode(message), jsonData);
2023-08-10 19:08:46 +08:00
switch (data['event']) {
case 'friend-chat-msg':
await receiveFriendMsg(data, true);
2023-08-11 23:02:31 +08:00
case 'apply-friend':
receiveApplyFriend(data);
case 'friend-added':
receiveFriendAdded(data);
case 'friend-deleted':
receiveFriendDeleted(data);
case 'chat-image':
receiveChatImages(data);
case 'group-chat-creation':
receiveGroupChatCreation(data);
case 'group-chat-msg':
receiveGroupChatMsg(data, true);
case 'pull-chat-image':
receivePullChatImage(data);
case 'chat-image-send-ok':
receiveCheckChatImage(data);
2023-08-10 19:08:46 +08:00
}
}
// This will be trigger while server or client close the connection
// for example server is restarting
2023-08-10 19:08:46 +08:00
void onDone() {
print('websocket disconnected <$channel>');
print('22222222222222222222');
2023-08-27 19:14:37 +08:00
if (socketStatus == SocketStatus.closed) {
// Client close the connection
2023-08-27 19:14:37 +08:00
return;
}
if (socketStatus == SocketStatus.connected) {
// Server close the connection
socketStatus = SocketStatus.reconnecting;
notifyListeners();
print(111111111111111);
reconnectTimes++;
reconnect();
}
if (reconnectTimes >= reconnectCount) {
socketStatus = SocketStatus.error;
reconnectTimes = 0;
} else {
socketStatus = SocketStatus.reconnecting;
print('3333333333333333333');
notifyListeners();
}
2023-08-10 19:08:46 +08:00
}
// This will be trigger while server exactly shutdown
void onError(Object error, StackTrace st) {
print('Websocket connect occurs error: <$error>');
// print(st);
if (reconnectTimes >= reconnectCount) {
socketStatus = SocketStatus.error;
notifyListeners();
channel.sink.close();
if (heartBeatTimer != null) {
heartBeatTimer!.cancel();
heartBeatTimer = null;
}
} else {
print('${reconnectTimes}th reconnection');
reconnect();
}
}
2023-08-10 19:08:46 +08:00
void heartBeatInspect() {
print('start heartbeat inspect......');
2023-08-10 19:08:46 +08:00
if (heartBeatTimer != null) {
heartBeatTimer!.cancel();
heartBeatTimer = null;
}
if (serverTimer != null) {
serverTimer!.cancel();
serverTimer = null;
}
heartBeatTimer = Timer(heartBeatTimeout, () {
2023-08-10 19:08:46 +08:00
channel.sink.add(json.encode({'event': 'ping'}));
serverTimer = Timer(heartBeatTimeout, () {
// This will trigger the onDone callback
2023-08-10 19:08:46 +08:00
channel.sink.close(status.internalServerError);
socketStatus = SocketStatus.reconnecting;
notifyListeners();
2023-08-10 19:08:46 +08:00
});
});
}
void reconnect() {
if (heartBeatTimer != null) {
heartBeatTimer!.cancel();
heartBeatTimer = null;
}
if (serverTimer != null) {
serverTimer!.cancel();
serverTimer = null;
}
if (reconnectTimer != null) {
reconnectTimer!.cancel();
reconnectTimer = null;
}
reconnectTimer = Timer(reconnectTimeout, () {
2023-08-10 19:08:46 +08:00
if (reconnectTimes < reconnectCount) {
print('websocket reconnecting......');
2023-08-10 19:08:46 +08:00
reconnectTimes++;
connect(id, true);
2023-08-10 19:08:46 +08:00
} else {
print('reconnection times exceed the max times......');
// If it is still disconnection after reconnect 30 times, set the socket
// status to error, means the network is bad, and stop reconnecting.
socketStatus = SocketStatus.error;
notifyListeners();
2023-08-10 19:08:46 +08:00
channel.sink.close();
}
});
}
2023-09-09 16:48:47 +08:00
void addSendImageTimer(String filename, int totalChunkNum) {
if (sendImageTimer.containsKey(filename)) {
sendImageTimer[filename]!.cancel();
sendImageTimer.remove(filename);
2023-09-09 16:48:47 +08:00
}
2023-08-11 23:02:31 +08:00
sendImageTimer[filename] = Timer(
heartBeatTimeout,
() async {
late Box<AttachmentProgress> attachmentLoadingBox;
try {
attachmentLoadingBox =
Hive.box<AttachmentProgress>('attachment_receive');
} catch (_) {
attachmentLoadingBox =
await Hive.openBox<AttachmentProgress>('attachment_receive');
}
AttachmentProgress? at = attachmentLoadingBox.get(filename);
if (at == null) {
attachmentLoadingBox.put(
filename,
AttachmentProgress(
0,
totalChunkNum,
0,
false,
true,
),
);
} else {
at.isPause = true;
attachmentLoadingBox.put(filename, at);
}
},
);
}
void removeSendImageTimer(String filename) {
if (sendImageTimer.containsKey(filename)) {
sendImageTimer[filename]!.cancel();
2023-09-09 16:48:47 +08:00
}
sendImageTimer.remove(filename);
2023-09-09 16:48:47 +08:00
}
}