cyberhybridhub/server/lib/signalr/questions_hub_connections.dart

124 lines
3.4 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'signalr_protocol.dart';
import 'text_message_format.dart';
/// Active SignalR connection for the questions hub.
class QuestionsHubConnection {
QuestionsHubConnection({
required this.connectionToken,
required this.firebaseUid,
required this.channel,
});
final String connectionToken;
final String firebaseUid;
final WebSocketChannel channel;
bool handshakeComplete = false;
StreamSubscription<Object?>? _subscription;
void listen(void Function(String message) onMessage, {void Function()? onDone}) {
_subscription = channel.stream.listen(
(Object? message) {
if (message is String) {
onMessage(message);
}
},
onDone: onDone,
cancelOnError: true,
);
}
Future<void> sendRaw(String payload) async {
channel.sink.add(payload);
}
Future<void> sendInvocation(String target, List<Object?> arguments) async {
await sendRaw(SignalrProtocol.invocation(
target: target,
arguments: arguments,
));
}
Future<void> close() async {
await _subscription?.cancel();
await channel.sink.close();
}
}
/// Tracks connected clients and delivers hub invocations by Firebase UID.
class QuestionsHubConnections {
final Map<String, QuestionsHubConnection> _byToken =
<String, QuestionsHubConnection>{};
final Map<String, Set<String>> _tokensByUid = <String, Set<String>>{};
void register(QuestionsHubConnection connection) {
_byToken[connection.connectionToken] = connection;
_tokensByUid
.putIfAbsent(connection.firebaseUid, () => <String>{})
.add(connection.connectionToken);
}
Future<void> unregister(String connectionToken) async {
final QuestionsHubConnection? connection = _byToken.remove(connectionToken);
if (connection == null) {
return;
}
final Set<String>? tokens = _tokensByUid[connection.firebaseUid];
tokens?.remove(connectionToken);
if (tokens != null && tokens.isEmpty) {
_tokensByUid.remove(connection.firebaseUid);
}
await connection.close();
}
bool isConnected(String firebaseUid) =>
(_tokensByUid[firebaseUid]?.isNotEmpty ?? false);
Future<void> pushQuestionToConnection(
QuestionsHubConnection connection,
Map<String, dynamic> question,
) async {
if (!connection.handshakeComplete) {
return;
}
await connection.sendInvocation('ReceiveQuestion', <Object?>[question]);
}
Future<int> pushQuestion(
String firebaseUid,
Map<String, dynamic> question,
) async {
final Set<String> tokens = _tokensByUid[firebaseUid] ?? <String>{};
var delivered = 0;
for (final String token in tokens) {
final QuestionsHubConnection? connection = _byToken[token];
if (connection == null || !connection.handshakeComplete) {
continue;
}
await connection.sendInvocation('ReceiveQuestion', <Object?>[question]);
delivered++;
}
return delivered;
}
/// Parses inbound frames after the handshake.
void handleClientMessage(
QuestionsHubConnection connection,
String payload,
) {
for (final String message in TextMessageFormat.parse(payload)) {
final Map<String, dynamic> json =
jsonDecode(message) as Map<String, dynamic>;
final int? type = json['type'] as int?;
if (type == 6) {
unawaited(connection.sendRaw(SignalrProtocol.ping()));
}
}
}
}