1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-30 00:36:28 +00:00
comunicmobile/lib/helpers/websocket_helper.dart

237 lines
6.7 KiB
Dart
Raw Normal View History

2020-04-18 13:24:57 +00:00
import 'dart:async';
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';
import 'package:comunic/models/ws_message.dart';
2020-04-20 14:23:33 +00:00
import 'package:flutter_webrtc/rtc_ice_candidate.dart';
import 'package:flutter_webrtc/rtc_session_description.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 {
2020-04-17 14:04:47 +00:00
static WebSocketChannel _ws;
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() {
2020-04-17 14:04:47 +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
static Future<String> _getWsToken() async =>
(await APIRequest(uri: "ws/token", needLogin: true).exec())
.assertOk()
.getObject()["token"];
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);
_ws.stream.listen(
// When we got data
2020-04-18 14:35:53 +00:00
(data) {
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();
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-20 06:47:08 +00:00
/// Close current WebSocket (if any)
static close() {
if (isConnected()) _ws.sink.close();
}
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");
_ws.sink.add(msg);
_requests[id] = completer;
return completer.future;
}
/// 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);
} 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
case "number_notifs":
EventsHelper.emit(NewNumberNotifsEvent(msg.data));
break;
2020-04-18 14:35:53 +00:00
// New number of unread conversations
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;
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;
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(
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;
// 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;
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") {
completer.completeError(Exception("Could not process request!"));
return;
}
completer.complete(msg.data);
}
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);