More wip on game creation

This commit is contained in:
Vincent Velociter
2023-06-14 17:35:02 +02:00
parent b116784488
commit 9aacf1189b
11 changed files with 143 additions and 82 deletions
-2
View File
@@ -44,7 +44,6 @@ dart_code_metrics:
- avoid-unnecessary-type-assertions
- avoid-unnecessary-type-casts
- avoid-unrelated-type-assertions
- prefer-iterable-of
- prefer-first
- prefer-immediate-return
- prefer-iterable-of
@@ -61,7 +60,6 @@ dart_code_metrics:
- avoid-shrink-wrap-in-lists
- avoid-unnecessary-setstate
- avoid-expanded-as-spacer
- avoid-wrapping-in-padding
- check-for-equals-in-render-object-setters
- consistent-update-render-object
- prefer-const-border-radius
+5
View File
@@ -8,6 +8,11 @@ const kLichessWSHost = String.fromEnvironment(
defaultValue: 'wss://socket.lichess.org',
);
const kLichessWSSecret = String.fromEnvironment(
'LICHESS_WS_SECRET',
defaultValue: 'somethingElseInProd',
);
const kLichessDevUser =
String.fromEnvironment('LICHESS_DEV_USER', defaultValue: 'lichess');
const kLichessDevPassword = String.fromEnvironment('LICHESS_DEV_PASSWORD');
+33 -50
View File
@@ -35,7 +35,8 @@ AuthClient authClient(AuthClientRef ref) {
crashlytics,
);
ref.onDispose(() {
authClient.close();
logger.info('Disposing AuthClient.');
httpClient.close();
});
return authClient;
}
@@ -83,18 +84,7 @@ class AuthClient {
Result.capture(
(retryOnError ? _retryClient : _client).get(url, headers: headers),
).mapError((error, stackTrace) {
_log.severe('Request error', error, stackTrace);
if (kReleaseMode) {
_crashlytics.recordError(
error,
stackTrace,
reason: 'a non-fatal http request error',
information: [
'url: $url',
'headers: $headers',
],
);
}
_recordError('GET', error, stackTrace, url, headers);
return GenericIOException();
}).flatMap(
(response) => _validateResponseStatusResult('GET', url, response),
@@ -111,18 +101,7 @@ class AuthClient {
(retryOnError ? _retryClient : _client)
.post(url, headers: headers, body: body, encoding: encoding),
).mapError((error, stackTrace) {
_log.severe('Request error', error, stackTrace);
if (kReleaseMode) {
_crashlytics.recordError(
error,
stackTrace,
reason: 'a non-fatal http request error',
information: [
'url: $url',
'headers: $headers',
],
);
}
_recordError('POST', error, stackTrace, url, headers);
return GenericIOException();
}).flatMap(
(response) => _validateResponseStatusResult('POST', url, response),
@@ -139,18 +118,7 @@ class AuthClient {
(retryOnError ? _retryClient : _client)
.delete(url, headers: headers, body: body, encoding: encoding),
).mapError((error, stackTrace) {
_log.severe('Request error', error, stackTrace);
if (kReleaseMode) {
_crashlytics.recordError(
error,
stackTrace,
reason: 'a non-fatal http request error',
information: [
'url: $url',
'headers: $headers',
],
);
}
_recordError('DELETE', error, stackTrace, url, headers);
return GenericIOException();
}).flatMap(
(response) => _validateResponseStatusResult('DELETE', url, response),
@@ -166,18 +134,7 @@ class AuthClient {
}
return Result.capture(_client.send(request))
.mapError((error, stackTrace) {
_log.severe('Request error', error, stackTrace);
if (kReleaseMode) {
_crashlytics.recordError(
error,
stackTrace,
reason: 'a non-fatal http request error',
information: [
'url: $url',
'headers: $headers',
],
);
}
_recordError('GET', error, stackTrace, url, headers);
return GenericIOException();
})
.flatMap((r) => _validateResponseStatusResult('GET', url, r))
@@ -223,8 +180,34 @@ class AuthClient {
);
}
void _recordError(
String method,
Object error,
StackTrace? stackTrace,
Uri url,
Map<String, String>? headers,
) {
// Ignore canceling seek requests
if (error is ClientException && url.path.contains('api/board/seek')) {
return;
}
_log.severe('Request error', error, stackTrace);
if (kReleaseMode) {
_crashlytics.recordError(
error,
stackTrace,
reason: 'a non-fatal http request error',
information: [
'method: $method',
'url: $url',
'headers: $headers',
],
);
}
}
void close() {
_log.info('Closing AuthClient.');
_retryClient.close();
_client.close();
}
}
+16 -4
View File
@@ -1,3 +1,5 @@
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';
@@ -25,6 +27,8 @@ AuthSocket authSocket(AuthSocketRef ref) {
return authSocket;
}
final hmacSha1 = Hmac(sha1, utf8.encode(kLichessWSSecret));
/// WebSocket channel wrapper to authenticate with lichess
///
/// It automatically generate a new SRI for each connection.
@@ -55,9 +59,12 @@ class AuthSocket {
final info = _ref.read(packageInfoProvider);
final sri = genRandomString(12);
final uri = Uri.parse('$kLichessWSHost$kWebSocketPath?sri=$sri');
final bearer = session != null
? '${session.token}:${hmacSha1.convert(utf8.encode(session.token))}'
: '';
final headers = session != null
? {
'Authorization': 'Bearer ${session.token}',
'Authorization': 'Bearer $bearer',
'User-Agent': AuthClient.userAgent(info, session.user),
}
: {
@@ -83,9 +90,14 @@ class AuthSocket {
///
/// Will not do anything if the channel is not connected.
void switchRoute(Uri route) {
final msg = "{ t: 'switch', d: { uri: '${route.path}' }}";
print('switch route: $msg');
sink?.add(msg);
sink?.add(
jsonEncode({
't': 'switch',
'd': {
'uri': '${route.path}?sri=$sri',
},
}),
);
}
/// Gets the current WebSocket sink
+70 -13
View File
@@ -1,11 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:async/async.dart';
import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:deep_pick/deep_pick.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/common/perf.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/speed.dart';
import 'package:lichess_mobile/src/model/game/game.dart';
import 'package:lichess_mobile/src/model/game/player.dart';
import 'package:lichess_mobile/src/model/lobby/lobby_repository.dart';
import 'package:lichess_mobile/src/model/lobby/game_seek.dart';
import 'package:lichess_mobile/src/model/auth/auth_client.dart';
import 'package:lichess_mobile/src/model/auth/auth_socket.dart';
import 'package:lichess_mobile/src/model/settings/play_preferences.dart';
@@ -32,28 +41,76 @@ class CreateGameService {
final completer = Completer<PlayableGame>();
final stream = socket.connect();
_socketSubscription = stream.listen((event) {
print('event: $event');
debugPrint('event: $event');
final msg = jsonDecode(event as String) as Map<String, dynamic>;
switch (msg['t'] as String) {
case 'redirect':
// ignore: avoid_dynamic_calls
final gameId = msg['d']['id'] as String;
socket.switchRoute(Uri(path: '/play/$gameId'));
case 'full':
final game =
// ignore: avoid_dynamic_calls
_playableGameFromJson(msg['d']['game'] as Map<String, dynamic>);
completer.complete(game);
}
});
socket.sink?.add('null');
socket.switchRoute(Uri(path: '/lobby/socket'));
socket.switchRoute(Uri(path: '/lobby/socket/v5'));
// await Result.release(
// lobbyRepo.createSeek(
// GameSeek(
// time: Duration(seconds: playPref.timeIncrement.time),
// increment: Duration(seconds: playPref.timeIncrement.increment),
// // TODO add rated choice
// rated: true,
// ),
// ),
// );
_log.info('Creating new online game');
await Result.release(
lobbyRepo.createSeek(
GameSeek(
time: Duration(seconds: playPref.timeIncrement.time),
increment: Duration(seconds: playPref.timeIncrement.increment),
// TODO add rated choice
rated: true,
),
sri: socket.sri!,
),
);
return completer.future;
}
void dispose() {
ref.invalidate(authClientProvider);
_socketSubscription?.cancel();
}
}
PlayableGame _playableGameFromJson(Map<String, dynamic> json) =>
_playableGameFromPick(pick(json).required());
PlayableGame _playableGameFromPick(RequiredPick pick) {
return PlayableGame(
id: pick('id').asGameIdOrThrow(),
rated: pick('rated').asBoolOrThrow(),
speed: pick('speed').asSpeedOrThrow(),
perf: pick('perf').asPerfOrThrow(),
player: pick('player').asSideOrThrow(),
status: pick('status').asGameStatusOrThrow(),
white: pick('players', 'white').letOrThrow(_playerFromUserGamePick),
black: pick('players', 'black').letOrThrow(_playerFromUserGamePick),
variant: pick('variant').asVariantOrThrow(),
fen: pick('fen').asStringOrThrow(),
initialFen: pick('initialFen').asStringOrNull(),
);
}
Player _playerFromUserGamePick(RequiredPick pick) {
return Player(
id: pick('user', 'id').asUserIdOrNull(),
name: pick('user', 'name').asStringOrNull() ?? 'Stockfish',
patron: pick('user', 'patron').asBoolOrNull(),
title: pick('user', 'title').asStringOrNull(),
rating: pick('rating').asIntOrNull(),
ratingDiff: pick('ratingDiff').asIntOrNull(),
aiLevel: pick('aiLevel').asIntOrNull(),
);
}
+3 -2
View File
@@ -22,8 +22,9 @@ class PlayableGame with _$PlayableGame {
required Variant variant,
required Speed speed,
required Perf perf,
required String initialFen,
required Side orientation,
required String fen,
String? initialFen,
required Side player,
required GameStatus status,
Move? lastMove,
int? turns,
+2 -2
View File
@@ -21,10 +21,10 @@ class LobbyRepository {
final AuthClient authClient;
FutureResult<void> createSeek(GameSeek seek) {
FutureResult<void> createSeek(GameSeek seek, {required String sri}) {
return authClient.post(
Uri.parse(
'$kLichessHost/api/board/seek',
'$kLichessHost/api/board/seek?sri=$sri',
),
body: seek.requestBody,
);
+1 -1
View File
@@ -18,7 +18,7 @@ import 'package:lichess_mobile/src/utils/immersive_mode.dart';
part 'online_game_screen.g.dart';
@riverpod
Future<PlayableGame> _playableGame(_PlayableGameRef ref) async {
Future<PlayableGame> _playableGame(_PlayableGameRef ref) {
final service = ref.watch(createGameServiceProvider);
ref.onDispose(service.dispose);
return service.newOnlineGame();
+11 -7
View File
@@ -240,15 +240,19 @@ class _ChoiceChip extends StatelessWidget {
? CupertinoColors.systemBackground
: CupertinoColors.secondarySystemBackground
.resolveFrom(context),
borderRadius: BorderRadius.circular(5.0),
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
border: selected
? Border.all(
color: CupertinoColors.activeBlue.resolveFrom(context),
width: 2.0,
? Border.fromBorderSide(
BorderSide(
color: CupertinoColors.activeBlue.resolveFrom(context),
width: 2.0,
),
)
: Border.all(
color: Colors.transparent,
width: 2.0,
: const Border.fromBorderSide(
BorderSide(
color: Colors.transparent,
width: 2.0,
),
),
),
child: Padding(
+1 -1
View File
@@ -283,7 +283,7 @@ packages:
source: hosted
version: "0.3.3+4"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+1
View File
@@ -60,6 +60,7 @@ dependencies:
flutter_displaymode: ^0.6.0
web_socket_channel: ^2.4.0
device_info_plus: ^9.0.2
crypto: ^3.0.3
dev_dependencies:
build_runner: ^2.3.2