Mostly working websocket stuff, some message weirdness at the moment...
This commit is contained in:
@@ -40,6 +40,11 @@ class JwtNotifier extends _$JwtNotifier {
|
||||
|
||||
state = AsyncValue.data(jwt);
|
||||
}
|
||||
|
||||
Future<void> eraseJwt() async {
|
||||
SharedPreferencesAsync().remove('jwt');
|
||||
state = AsyncValue.data(null);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
@@ -52,8 +57,7 @@ JWTBody? jwtBody(Ref ref) {
|
||||
final payload = JwtDecoder.tryDecode(jwtString);
|
||||
if (payload == null) {
|
||||
logger.fine('Failed to decode JWT, removing key.');
|
||||
SharedPreferencesAsync().remove('jwt');
|
||||
ref.invalidate(jwtNotifierProvider);
|
||||
ref.read(jwtNotifierProvider.notifier).eraseJwt();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:frontend/providers/auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_models/jwt.dart';
|
||||
|
||||
part 'dio.g.dart';
|
||||
|
||||
@@ -17,8 +18,13 @@ Dio dio(Ref ref) {
|
||||
));
|
||||
|
||||
final jwt = ref.watch(jwtNotifierProvider).valueOrNull;
|
||||
final jwtBody = ref.read(jwtBodyProvider);
|
||||
|
||||
dio.interceptors.addAll([JwtInterceptor(jwt: jwt), CustomLogInterceptor()]);
|
||||
dio.interceptors.addAll([
|
||||
JwtInterceptor(
|
||||
jwt: jwt, jwtBody: jwtBody, invalidateJwtCallback: () => ref.read(jwtNotifierProvider.notifier).eraseJwt()),
|
||||
CustomLogInterceptor()
|
||||
]);
|
||||
|
||||
logger.fine('Created new Dio object');
|
||||
|
||||
@@ -28,15 +34,36 @@ Dio dio(Ref ref) {
|
||||
// Adds the jwt to
|
||||
class JwtInterceptor extends Interceptor {
|
||||
final String? jwt;
|
||||
final JWTBody? jwtBody;
|
||||
final Function invalidateJwtCallback;
|
||||
|
||||
JwtInterceptor({required this.jwt});
|
||||
JwtInterceptor({required this.jwt, required this.jwtBody, required this.invalidateJwtCallback});
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (jwt == null || jwtBody == null) {
|
||||
handler.next(options);
|
||||
return;
|
||||
}
|
||||
if (jwtBody != null && jwtBody!.exp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
invalidateJwtCallback();
|
||||
handler.next(options);
|
||||
return;
|
||||
}
|
||||
if (jwt != null) {
|
||||
options.headers['Authorization'] = 'Bearer $jwt';
|
||||
handler.next(options);
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
// on unauthorized request, remove jwt
|
||||
@override
|
||||
// ignore: strict_raw_type
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
if (response.statusCode == 401) {
|
||||
invalidateJwtCallback();
|
||||
}
|
||||
handler.next(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:frontend/providers/dio.dart';
|
||||
import 'package:frontend/providers/web_socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_models/room.dart';
|
||||
|
||||
part 'game_messages.g.dart';
|
||||
|
||||
final _logger = Logger('GameMessageNotifier');
|
||||
|
||||
@riverpod
|
||||
class GameMessageNotifier extends _$GameMessageNotifier {
|
||||
StreamSubscription<GameRoomMessage?>? _sub;
|
||||
|
||||
@override
|
||||
Stream<GameRoomMessage> build() {
|
||||
return Stream.empty();
|
||||
}
|
||||
Stream<GameRoomMessage?> build() {
|
||||
final Stream<dynamic>? stream = ref.watch(webSocketStreamProvider);
|
||||
if (stream == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
Future<void> connect(String gameRoomUuid) async {
|
||||
final dio = ref.read(dioProvider);
|
||||
Uri.parse('ws://localhost:8080/room/$gameRoomUuid/ws');
|
||||
|
||||
// connect to websocket and then set stream of websocket to state
|
||||
final wsUrl = Uri.parse('ws://localhost:8080/room/$gameRoomUuid/ws');
|
||||
|
||||
final connection = await WebSocket.connect(wsUrl.toString());
|
||||
|
||||
final Stream<GameRoomMessage> gameRoomStream = connection.map((message) {
|
||||
final Stream<GameRoomMessage?> gameRoomStream = stream.map((message) {
|
||||
try {
|
||||
if (message is String) {
|
||||
GameRoomMessage.fromJson(jsonDecode(message) as Map<String, dynamic>);
|
||||
return GameRoomMessage.fromJson(jsonDecode(message) as Map<String, dynamic>);
|
||||
} else {
|
||||
logger.info('Recieved non-string message in socket: $message');
|
||||
_logger.info('Recieved non-string message in socket: $message');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error parsing message: $e');
|
||||
rethrow;
|
||||
_logger.severe('Error parsing message: `${message.runtimeType}` $message', e, StackTrace.current);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
gameRoomStream.listen(
|
||||
_sub = gameRoomStream.listen(
|
||||
(event) => state = AsyncValue.data(event),
|
||||
);
|
||||
return gameRoomStream;
|
||||
}
|
||||
|
||||
// Cancel the gameroom stream subscription
|
||||
void close() {
|
||||
_sub?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:frontend/providers/auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'web_socket.g.dart';
|
||||
|
||||
final _logger = Logger('WebSocketNotifier');
|
||||
|
||||
@riverpod
|
||||
class WebSocketNotifier extends _$WebSocketNotifier {
|
||||
@override
|
||||
Future<WebSocket?> build() async {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
state = AsyncValue.loading();
|
||||
final jwt = ref.read(jwtNotifierProvider).valueOrNull;
|
||||
final jwtBody = ref.read(jwtBodyProvider);
|
||||
if (jwt == null || jwtBody == null) {
|
||||
_logger.warning('Tried to connect to ws without jwt token');
|
||||
return;
|
||||
}
|
||||
|
||||
final wsUrl = Uri.parse('ws://localhost:8080/room/${jwtBody.roomCode}/ws');
|
||||
_logger.finest('Attempting to connect to $wsUrl');
|
||||
|
||||
try {
|
||||
final connection = await WebSocket.connect(wsUrl.toString(), headers: {'Authorization': 'Bearer: $jwt'});
|
||||
_logger.fine('Client ws connection established to room ${jwtBody.roomUuid}');
|
||||
state = AsyncValue.data(connection);
|
||||
} catch (e) {
|
||||
if (e is WebSocketException) {
|
||||
if (e.httpStatusCode == 401) {
|
||||
ref.read(jwtNotifierProvider.notifier).eraseJwt();
|
||||
}
|
||||
}
|
||||
_logger.warning('Error occurred creating web socket: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @riverpod
|
||||
// class WebSocketStreamNotifier extends _$WebSocketStreamNotifier {
|
||||
// @override
|
||||
// Stream<dynamic> build() {
|
||||
// final connection = ref.watch(webSocketNotifierProvider).valueOrNull;
|
||||
// if (connection == null) return Stream.empty();
|
||||
// _logger.finest('Created broadcast stream from ws connection');
|
||||
// return connection.asBroadcastStream();
|
||||
// }
|
||||
// }
|
||||
|
||||
@riverpod
|
||||
Raw<Stream<dynamic>> webSocketStream(Ref ref) {
|
||||
final connection = ref.watch(webSocketNotifierProvider).valueOrNull;
|
||||
if (connection == null) return Stream.empty();
|
||||
_logger.finest('Created broadcast stream from ws connection');
|
||||
return connection.asBroadcastStream();
|
||||
}
|
||||
Reference in New Issue
Block a user