2020-04-18 13:24:57 +00:00
|
|
|
import 'dart:async';
|
2020-04-18 12:14:54 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2020-04-18 14:35:53 +00:00
|
|
|
import 'package:comunic/helpers/comments_helper.dart';
|
2020-04-19 11:58:24 +00:00
|
|
|
import 'package:comunic/helpers/conversations_helper.dart';
|
2020-04-18 11:48:21 +00:00
|
|
|
import 'package:comunic/helpers/events_helper.dart';
|
2020-04-17 14:04:47 +00:00
|
|
|
import 'package:comunic/models/api_request.dart';
|
|
|
|
import 'package:comunic/models/config.dart';
|
2020-04-18 12:14:54 +00:00
|
|
|
import 'package:comunic/models/ws_message.dart';
|
2021-02-07 16:09:08 +00:00
|
|
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
2020-04-17 14:04:47 +00:00
|
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
|
|
|
2020-04-17 13:25:26 +00:00
|
|
|
/// User web socket helper
|
|
|
|
///
|
|
|
|
/// @author Pierre Hubert
|
|
|
|
|
|
|
|
class WebSocketHelper {
|
2022-03-10 18:39:57 +00:00
|
|
|
static WebSocketChannel? _ws;
|
2020-04-17 14:04:47 +00:00
|
|
|
|
2020-04-18 13:24:57 +00:00
|
|
|
static int _counter = 0;
|
|
|
|
|
|
|
|
static final _requests = Map<String, Completer<dynamic>>();
|
|
|
|
|
2020-04-17 14:04:47 +00:00
|
|
|
/// Check out whether we are currently connected to WebSocket or not
|
2020-04-17 13:25:26 +00:00
|
|
|
static bool isConnected() {
|
2022-03-10 18:39:57 +00:00
|
|
|
return _ws != null && _ws!.closeCode == null;
|
2020-04-17 13:25:26 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 14:04:47 +00:00
|
|
|
/// Get WebSocket access token
|
2022-03-10 18:39:57 +00:00
|
|
|
static Future<String?> _getWsToken() async =>
|
2020-04-17 14:04:47 +00:00
|
|
|
(await APIRequest(uri: "ws/token", needLogin: true).exec())
|
|
|
|
.assertOk()
|
2022-03-10 19:36:55 +00:00
|
|
|
.getObject()["token"];
|
2020-04-17 14:04:47 +00:00
|
|
|
|
2020-04-17 13:25:26 +00:00
|
|
|
/// Connect to WebSocket
|
|
|
|
static connect() async {
|
|
|
|
if (isConnected()) return;
|
2020-04-17 14:04:47 +00:00
|
|
|
|
|
|
|
// First, get an access token
|
|
|
|
final token = await _getWsToken();
|
|
|
|
|
2020-04-18 11:48:21 +00:00
|
|
|
// Determine WebSocket URI
|
2020-04-17 14:04:47 +00:00
|
|
|
final wsURL =
|
2020-04-18 14:35:53 +00:00
|
|
|
"${(config().apiServerSecure ? "wss" : "ws")}://${config().apiServerName}${config().apiServerUri}ws?token=$token";
|
2020-04-17 14:04:47 +00:00
|
|
|
final wsURI = Uri.parse(wsURL);
|
|
|
|
|
|
|
|
// Connect
|
|
|
|
_ws = WebSocketChannel.connect(wsURI);
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
_ws!.stream.listen(
|
2020-04-17 14:04:47 +00:00
|
|
|
// When we got data
|
2020-04-18 14:35:53 +00:00
|
|
|
(data) {
|
2020-04-18 12:14:54 +00:00
|
|
|
print("WS New data: $data");
|
|
|
|
_processMessage(data.toString());
|
|
|
|
},
|
2020-04-17 14:04:47 +00:00
|
|
|
|
|
|
|
// Print errors on console
|
|
|
|
onError: (e, stack) {
|
|
|
|
print("WS error! $e");
|
|
|
|
print(stack);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Notify when the channel is closed
|
2020-04-18 11:48:21 +00:00
|
|
|
onDone: () {
|
|
|
|
print("WS Channel closed");
|
2020-04-18 13:24:57 +00:00
|
|
|
|
|
|
|
// Clear Futures queue
|
|
|
|
_requests.clear();
|
|
|
|
|
2021-02-18 18:36:51 +00:00
|
|
|
_ws = null;
|
2020-04-18 11:48:21 +00:00
|
|
|
EventsHelper.emit(WSClosedEvent());
|
|
|
|
},
|
2020-04-17 14:04:47 +00:00
|
|
|
);
|
2020-04-17 13:25:26 +00:00
|
|
|
}
|
2020-04-18 12:14:54 +00:00
|
|
|
|
2020-04-20 06:47:08 +00:00
|
|
|
/// Close current WebSocket (if any)
|
|
|
|
static close() {
|
2022-03-10 18:39:57 +00:00
|
|
|
if (isConnected()) _ws!.sink.close();
|
2020-04-20 06:47:08 +00:00
|
|
|
}
|
|
|
|
|
2020-04-18 13:24:57 +00:00
|
|
|
/// Send a new message
|
|
|
|
///
|
|
|
|
/// This method might throw an [Exception] in case of failure
|
|
|
|
static Future<dynamic> sendMessage(String title, dynamic data) {
|
|
|
|
if (!isConnected())
|
|
|
|
throw Exception("WS: Trying to send message but websocket is closed!");
|
|
|
|
|
|
|
|
final completer = Completer();
|
|
|
|
|
|
|
|
final id = "freq-${_counter++}";
|
|
|
|
|
|
|
|
final msg = WsMessage(id: id, title: title, data: data).toJSON();
|
|
|
|
|
|
|
|
print("WS Send message: $msg");
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
_ws!.sink.add(msg);
|
2020-04-18 13:24:57 +00:00
|
|
|
|
|
|
|
_requests[id] = completer;
|
|
|
|
return completer.future;
|
|
|
|
}
|
|
|
|
|
2020-04-18 12:14:54 +00:00
|
|
|
/// Process incoming message
|
|
|
|
static _processMessage(String msgStr) {
|
|
|
|
try {
|
|
|
|
final msg = WsMessage.fromJSON(jsonDecode(msgStr));
|
|
|
|
|
|
|
|
if (!msg.hasId)
|
|
|
|
_processUnattendedMessage(msg);
|
|
|
|
else
|
2020-04-18 13:24:57 +00:00
|
|
|
_respondRequests(msg);
|
2020-04-18 12:14:54 +00:00
|
|
|
} catch (e, stack) {
|
|
|
|
print("WS could not process message: $e");
|
|
|
|
print(stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Process an unattended message
|
|
|
|
static _processUnattendedMessage(WsMessage msg) {
|
|
|
|
switch (msg.title) {
|
2020-04-18 14:35:53 +00:00
|
|
|
// New number of notifications
|
2020-04-18 12:14:54 +00:00
|
|
|
case "number_notifs":
|
|
|
|
EventsHelper.emit(NewNumberNotifsEvent(msg.data));
|
|
|
|
break;
|
|
|
|
|
2020-04-18 14:35:53 +00:00
|
|
|
// New number of unread conversations
|
2020-04-18 12:33:07 +00:00
|
|
|
case "number_unread_conversations":
|
|
|
|
EventsHelper.emit(NewNumberUnreadConversations(msg.data));
|
|
|
|
break;
|
|
|
|
|
2020-04-18 14:35:53 +00:00
|
|
|
// New comment
|
|
|
|
case "new_comment":
|
|
|
|
EventsHelper.emit(
|
|
|
|
NewCommentEvent(CommentsHelper.apiToComment(msg.data)));
|
|
|
|
break;
|
|
|
|
|
2020-04-18 14:46:55 +00:00
|
|
|
// Updated comment
|
|
|
|
case "comment_updated":
|
|
|
|
EventsHelper.emit(
|
|
|
|
UpdatedCommentEvent(CommentsHelper.apiToComment(msg.data)));
|
|
|
|
break;
|
|
|
|
|
2020-04-18 14:57:00 +00:00
|
|
|
// Deleted comment
|
|
|
|
case "comment_deleted":
|
|
|
|
EventsHelper.emit(DeletedCommentEvent(msg.data));
|
|
|
|
break;
|
|
|
|
|
2021-03-13 08:29:54 +00:00
|
|
|
// A user is writing a new message
|
|
|
|
case "writing_message_in_conv":
|
|
|
|
EventsHelper.emit(WritingMessageInConversationEvent(
|
|
|
|
msg.data["conv_id"], msg.data["user_id"]));
|
|
|
|
break;
|
|
|
|
|
2020-04-19 11:58:24 +00:00
|
|
|
// Created new conversation message
|
|
|
|
case "new_conv_message":
|
|
|
|
EventsHelper.emit(NewConversationMessageEvent(
|
|
|
|
ConversationsHelper.apiToConversationMessage(msg.data)));
|
|
|
|
break;
|
|
|
|
|
2020-04-19 12:16:35 +00:00
|
|
|
// Update conversation message content
|
|
|
|
case "updated_conv_message":
|
|
|
|
EventsHelper.emit(UpdatedConversationMessageEvent(
|
|
|
|
ConversationsHelper.apiToConversationMessage(msg.data)));
|
|
|
|
break;
|
|
|
|
|
2020-04-19 12:29:01 +00:00
|
|
|
// Deleted a conversation message
|
|
|
|
case "deleted_conv_message":
|
|
|
|
EventsHelper.emit(DeletedConversationMessageEvent(
|
|
|
|
ConversationsHelper.apiToConversationMessage(msg.data)));
|
|
|
|
break;
|
|
|
|
|
2021-03-13 11:16:57 +00:00
|
|
|
// Removed user from conversation
|
|
|
|
case "removed_user_from_conv":
|
2021-03-13 12:52:18 +00:00
|
|
|
EventsHelper.emit(RemovedUserFromConversationEvent(
|
2021-03-13 11:16:57 +00:00
|
|
|
msg.data["conv_id"], msg.data["user_id"]));
|
|
|
|
break;
|
|
|
|
|
2021-03-13 12:52:18 +00:00
|
|
|
// Conversation deleted
|
|
|
|
case "deleted_conversation":
|
|
|
|
EventsHelper.emit(DeletedConversationEvent(msg.data));
|
|
|
|
break;
|
|
|
|
|
2020-04-20 12:24:35 +00:00
|
|
|
// A user joined a call
|
|
|
|
case "user_joined_call":
|
|
|
|
EventsHelper.emit(
|
|
|
|
UserJoinedCallEvent(msg.data["callID"], msg.data["userID"]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
// A user left a call
|
|
|
|
case "user_left_call":
|
|
|
|
EventsHelper.emit(
|
|
|
|
UserLeftCallEvent(msg.data["callID"], msg.data["userID"]));
|
|
|
|
break;
|
|
|
|
|
2020-04-20 14:23:33 +00:00
|
|
|
// Got new call signal
|
|
|
|
case "new_call_signal":
|
|
|
|
Map<String, dynamic> signalData = msg.data["data"];
|
|
|
|
EventsHelper.emit(NewCallSignalEvent(
|
|
|
|
callID: msg.data["callID"],
|
|
|
|
peerID: msg.data["peerID"],
|
|
|
|
candidate: signalData.containsKey("candidate")
|
|
|
|
? RTCIceCandidate(
|
2020-04-20 15:24:42 +00:00
|
|
|
signalData["candidate"],
|
|
|
|
"${signalData["sdpMLineIndex"]}" /* fix plugin crash */,
|
|
|
|
signalData["sdpMLineIndex"])
|
2020-04-20 14:23:33 +00:00
|
|
|
: null,
|
|
|
|
sessionDescription: signalData.containsKey("type")
|
|
|
|
? RTCSessionDescription(signalData["sdp"], signalData["type"])
|
|
|
|
: null));
|
|
|
|
break;
|
|
|
|
|
2020-04-20 12:58:23 +00:00
|
|
|
// Call peer ready event
|
|
|
|
case "call_peer_ready":
|
|
|
|
EventsHelper.emit(
|
|
|
|
CallPeerReadyEvent(msg.data["callID"], msg.data["peerID"]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Call peer interrupted streaming event
|
|
|
|
case "call_peer_interrupted_streaming":
|
|
|
|
EventsHelper.emit(CallPeerInterruptedStreamingEvent(
|
|
|
|
msg.data["callID"], msg.data["peerID"]));
|
|
|
|
break;
|
|
|
|
|
|
|
|
// The call has been closed
|
2020-04-20 12:32:57 +00:00
|
|
|
case "call_closed":
|
|
|
|
EventsHelper.emit(CallClosedEvent(msg.data));
|
|
|
|
break;
|
|
|
|
|
2020-04-18 12:14:54 +00:00
|
|
|
default:
|
|
|
|
throw Exception("Unknown message type: ${msg.title}");
|
|
|
|
}
|
|
|
|
}
|
2020-04-18 13:24:57 +00:00
|
|
|
|
|
|
|
/// Process responses to requests
|
|
|
|
static _respondRequests(WsMessage msg) {
|
|
|
|
if (!_requests.containsKey(msg.id))
|
|
|
|
throw Exception(
|
|
|
|
"Could not find request ${msg.id} in the requests queue!");
|
|
|
|
|
|
|
|
final completer = _requests.remove(msg.id);
|
|
|
|
|
|
|
|
// Handles errors
|
|
|
|
if (msg.title != "success") {
|
2022-03-10 18:39:57 +00:00
|
|
|
completer!.completeError(Exception("Could not process request!"));
|
2020-04-18 13:24:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
completer!.complete(msg.data);
|
2020-04-18 13:24:57 +00:00
|
|
|
}
|
2020-04-17 13:25:26 +00:00
|
|
|
}
|
2020-04-18 13:24:57 +00:00
|
|
|
|
|
|
|
/// Helper function
|
|
|
|
Future<dynamic> ws(String title, dynamic data) =>
|
|
|
|
WebSocketHelper.sendMessage(title, data);
|