Skip to content

Commit

Permalink
Retry request on 429 and renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
veloce committed Feb 21, 2024
1 parent 6074fd6 commit 76038e6
Show file tree
Hide file tree
Showing 40 changed files with 134 additions and 116 deletions.
4 changes: 2 additions & 2 deletions lib/src/model/account/account_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class AccountPreferences extends _$AccountPreferences {
}

try {
return ref.withAuthClient(
return ref.withClient(
(client) => AccountRepository(client).getPreferences(),
);
} catch (e) {
Expand All @@ -91,7 +91,7 @@ class AccountPreferences extends _$AccountPreferences {

Future<void> _setPref<T>(String key, AccountPref<T> value) async {
await Future<void>.delayed(const Duration(milliseconds: 200));
await ref.withAuthClient(
await ref.withClient(
(client) => AccountRepository(client).setPreference(key, value),
);
ref.invalidateSelf();
Expand Down
8 changes: 4 additions & 4 deletions lib/src/model/account/account_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Future<User?> account(AccountRef ref) async {
final session = ref.watch(authSessionProvider);
if (session == null) return null;

return ref.withAuthClientCacheFor(
return ref.withClientCacheFor(
(client) => AccountRepository(client).getProfile(),
const Duration(hours: 1),
);
Expand All @@ -41,7 +41,7 @@ Future<LightUser?> accountUser(AccountUserRef ref) async {
Future<IList<UserActivity>> accountActivity(AccountActivityRef ref) async {
final session = ref.watch(authSessionProvider);
if (session == null) return IList();
return ref.withAuthClientCacheFor(
return ref.withClientCacheFor(
(client) => UserRepository(client).getActivity(session.user.id),
const Duration(hours: 1),
);
Expand All @@ -53,7 +53,7 @@ Future<IList<LightArchivedGame>> accountRecentGames(
) async {
final session = ref.watch(authSessionProvider);
if (session == null) return IList();
return ref.withAuthClientCacheFor(
return ref.withClientCacheFor(
(client) => GameRepository(client).getRecentGames(session.user.id),
const Duration(hours: 1),
);
Expand All @@ -64,7 +64,7 @@ Future<IList<OngoingGame>> ongoingGames(OngoingGamesRef ref) async {
final session = ref.watch(authSessionProvider);
if (session == null) return IList();

return ref.withAuthClientCacheFor(
return ref.withClientCacheFor(
(client) => AccountRepository(client).getOngoingGames(),
const Duration(hours: 1),
);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/analysis/server_analysis_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class ServerAnalysisService {
);

try {
await ref.withAuthClient(
await ref.withClient(
(client) => GameRepository(client).requestServerAnalysis(id.gameId),
);
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions lib/src/model/auth/auth_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AuthController extends _$AuthController {

try {
final session = await ref
.withAuthClient((client) => AuthRepository(client, appAuth).signIn());
.withClient((client) => AuthRepository(client, appAuth).signIn());

ref.read(authSessionProvider.notifier).update(session);
ref.read(notificationServiceProvider).registerDevice();
Expand All @@ -39,7 +39,7 @@ class AuthController extends _$AuthController {
final appAuth = ref.read(appAuthProvider);

try {
await ref.withAuthClient(
await ref.withClient(
(client) => AuthRepository(client, appAuth).signOut(),
);
ref.read(notificationServiceProvider).unregister();
Expand Down
52 changes: 35 additions & 17 deletions lib/src/model/common/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:http/http.dart'
Response,
StreamedResponse;
import 'package:http/io_client.dart';
import 'package:http/retry.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/model/auth/bearer.dart';
import 'package:lichess_mobile/src/model/common/socket.dart';
Expand Down Expand Up @@ -60,17 +61,32 @@ Client httpClient(PackageInfo pInfo) {
}

@Riverpod(keepAlive: true)
AuthClientFactory authClientFactory(AuthClientFactoryRef ref) {
return AuthClientFactory(ref);
LichessClientFactory lichessClientFactory(LichessClientFactoryRef ref) {
return LichessClientFactory(ref);
}

class AuthClientFactory {
AuthClientFactory(this._ref);
/// Factory for the default [Client] used to send requests to lichess server.
///
/// It creates a client that:
/// - Retries just once, after 500ms, on 429 Too Many Requests.
/// - Sets the Authorization header when a token has been stored.
/// - Sets the user-agent header with the app version, build number, and device info.
/// - Logs all requests and responses with status code >= 400.
///
/// This class should be overridden in tests to provide a mock client.
class LichessClientFactory {
LichessClientFactory(this._ref);

final AuthClientFactoryRef _ref;
final LichessClientFactoryRef _ref;

Client call() {
return AuthClient(httpClient(_ref.read(packageInfoProvider)), _ref);
// Retry just once, after 500ms, on 429 Too Many Requests.
// TODO consider throttling the requests, instead of retrying.
return RetryClient(
AuthClient(httpClient(_ref.read(packageInfoProvider)), _ref),
retries: 1,
when: (response) => response.statusCode == 429,
);
}
}

Expand All @@ -95,7 +111,7 @@ String userAgent(UserAgentRef ref) {
class AuthClient extends BaseClient {
AuthClient(this._inner, this._ref);

final AuthClientFactoryRef _ref;
final LichessClientFactoryRef _ref;
final Client _inner;

@override
Expand Down Expand Up @@ -338,7 +354,7 @@ class _ReuseClientService {
/// Returns the client and a unique key to be used to close it later.
///
/// If the client is null, it creates a new one.
(Client, UniqueKey) get(AuthClientFactory factory) {
(Client, UniqueKey) get(LichessClientFactory factory) {
if (_client == null) {
_logger.info('Creating a new client.');
}
Expand Down Expand Up @@ -370,14 +386,14 @@ class _ReuseClientService {
}

extension ClientWidgetRefExtension on WidgetRef {
/// Runs [fn] with an [AuthClient].
/// Runs [fn] with a [Client] configured to send requests to lichess server.
///
/// It handles the creation and closing of the client, while trying to reuse
/// the same client for multiple requests at the same time.
/// Will try to close the client after [fn] completes.
Future<T> withAuthClient<T>(Future<T> Function(Client) fn) async {
Future<T> withClient<T>(Future<T> Function(Client) fn) async {
final (client, key) =
_ReuseClientService.instance.get(read(authClientFactoryProvider));
_ReuseClientService.instance.get(read(lichessClientFactoryProvider));
try {
return await fn(client);
} finally {
Expand All @@ -387,15 +403,15 @@ extension ClientWidgetRefExtension on WidgetRef {
}

extension ClientRefExtension on Ref {
/// Runs [fn] with an [AuthClient].
/// Runs [fn] with an [Client] configured to send requests to lichess server.
///
/// It handles the creation and closing of the client, while trying to reuse
/// the same client for multiple requests at the same time.
/// Will try to close the client after [fn] completes, or when the provider is
/// disposed.
Future<T> withAuthClient<T>(Future<T> Function(Client) fn) async {
Future<T> withClient<T>(Future<T> Function(Client) fn) async {
final (client, key) =
_ReuseClientService.instance.get(read(authClientFactoryProvider));
_ReuseClientService.instance.get(read(lichessClientFactoryProvider));
onDispose(() => _ReuseClientService.instance.close(key));
try {
return await fn(client);
Expand All @@ -406,7 +422,9 @@ extension ClientRefExtension on Ref {
}

extension ClientAutoDisposeRefExtension<T> on AutoDisposeRef<T> {
/// Runs [fn] with an [AuthClient] and keeps the provider alive for [duration].
/// Runs [fn] with an [Client] configured to send requests to lichess server,
/// and keeps the provider alive for [duration].
///
/// This is primarily used for caching network requests in a [FutureProvider].
///
/// It handles the creation and closing of the client, while trying to reuse
Expand All @@ -416,14 +434,14 @@ extension ClientAutoDisposeRefExtension<T> on AutoDisposeRef<T> {
///
/// If [fn] throws with a [SocketException], the provider is not kept alive, this
/// allows to retry the request later.
Future<U> withAuthClientCacheFor<U>(
Future<U> withClientCacheFor<U>(
Future<U> Function(Client) fn,
Duration duration,
) async {
final link = keepAlive();
final timer = Timer(duration, link.close);
final (client, key) =
_ReuseClientService.instance.get(read(authClientFactoryProvider));
_ReuseClientService.instance.get(read(lichessClientFactoryProvider));
onDispose(() {
_ReuseClientService.instance.close(key);
timer.cancel();
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/correspondence/correspondence_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class CorrespondenceService {
final storedOngoingGames =
await _storage.fetchOngoingGames(_session?.user.id);

ref.withAuthClient((client) async {
ref.withClient((client) async {
try {
final accountRepository = AccountRepository(client);
final gameRepository = GameRepository(client);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/game/game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ class GameController extends _$GameController {

FutureResult<ArchivedGame> _getPostGameData() {
return Result.capture(
ref.withAuthClient(
ref.withClient(
(client) => GameRepository(client).getGame(gameFullId.gameId),
),
);
Expand Down
4 changes: 2 additions & 2 deletions lib/src/model/game/game_repository_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ part 'game_repository_providers.g.dart';

@riverpod
Future<ArchivedGame> archivedGame(ArchivedGameRef ref, {required GameId id}) {
return ref.withAuthClient(
return ref.withClient(
(client) => GameRepository(client).getGame(id),
);
}
Expand All @@ -20,7 +20,7 @@ Future<IList<LightArchivedGame>> gamesById(
GamesByIdRef ref, {
required ISet<GameId> ids,
}) {
return ref.withAuthClient(
return ref.withClient(
(client) => GameRepository(client).getGamesByIds(ids),
);
}
8 changes: 4 additions & 4 deletions lib/src/model/game/game_share_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class GameShareService {

/// Fetches the raw PGN of a game and launches the share dialog.
Future<void> rawPgn(GameId id) async {
final resp = await _ref.withAuthClient(
final resp = await _ref.withClient(
(client) => client
.get(
Uri.parse(
Expand All @@ -39,7 +39,7 @@ class GameShareService {

/// Fetches the annotated PGN of a game and launches the share dialog.
Future<void> annotatedPgn(GameId id) async {
final resp = await _ref.withAuthClient(
final resp = await _ref.withClient(
(client) => client
.get(
Uri.parse(
Expand All @@ -63,7 +63,7 @@ class GameShareService {
) async {
final boardTheme = _ref.read(boardPreferencesProvider).boardTheme;
final pieceTheme = _ref.read(boardPreferencesProvider).pieceSet;
final resp = await _ref.withAuthClient(
final resp = await _ref.withClient(
(client) => client
.get(
Uri.parse(
Expand All @@ -84,7 +84,7 @@ class GameShareService {
Future<void> gameGif(GameId id, Side orientation) async {
final boardTheme = _ref.read(boardPreferencesProvider).boardTheme;
final pieceTheme = _ref.read(boardPreferencesProvider).pieceSet;
final resp = await _ref.withAuthClient(
final resp = await _ref.withClient(
(client) => client
.get(
Uri.parse(
Expand Down
4 changes: 2 additions & 2 deletions lib/src/model/lobby/create_game_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CreateGameService {
final completer = Completer<GameFullId>()..future.whenComplete(_close);

_pendingGameConnection = (
ref.read(authClientFactoryProvider)(),
ref.read(lichessClientFactoryProvider)(),
stream.listen((event) {
if (event.topic == 'redirect') {
final data = event.data as Map<String, dynamic>;
Expand Down Expand Up @@ -81,7 +81,7 @@ class CreateGameService {
Future<void> newCorrespondenceGame(GameSeek seek) async {
_log.info('Creating new correspondence game');

await ref.withAuthClient(
await ref.withClient(
(client) => LobbyRepository(client).createSeek(
seek,
sri: ref.read(socketClientProvider).sri,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/lobby/lobby_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ part 'lobby_repository.g.dart';
Future<IList<CorrespondenceChallenge>> correspondenceChallenges(
CorrespondenceChallengesRef ref,
) {
final client = ref.read(authClientFactoryProvider)();
final client = ref.read(lichessClientFactoryProvider)();
ref.onDispose(client.close);

final lobbyRepository = LobbyRepository(client);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/puzzle/puzzle_activity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class PuzzleActivity extends _$PuzzleActivity {
if (_list.length < _maxPuzzles) {
state = AsyncData(currentVal.copyWith(isLoading: true));
Result.capture(
ref.withAuthClient(
ref.withClient(
(client) => PuzzleRepository(client)
.puzzleActivity(_nbPerPage, before: _list.last.date),
),
Expand Down
8 changes: 4 additions & 4 deletions lib/src/model/puzzle/puzzle_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ class PuzzleController extends _$PuzzleController {
.setDifficulty(difficulty);

// ignore: avoid_manual_providers_as_generated_provider_dependency
final nextPuzzle = await ref.withAuthClient(
final nextPuzzle = await ref.withClient(
(client) => _service(client).resetBatch(
userId: initialContext.userId,
angle: initialContext.angle,
Expand All @@ -239,7 +239,7 @@ class PuzzleController extends _$PuzzleController {
if (initialContext.userId != null) {
final streak = state.streak?.index;
if (streak != null && streak > 0) {
ref.withAuthClient(
ref.withClient(
(client) => _repository(client).postStreakRun(streak),
);
}
Expand Down Expand Up @@ -285,7 +285,7 @@ class PuzzleController extends _$PuzzleController {
FutureResult<PuzzleContext?> _fetchNextStreakPuzzle(PuzzleStreak streak) {
return streak.nextId != null
? Result.capture(
ref.withAuthClient(
ref.withClient(
(client) => _repository(client).fetch(streak.nextId!).then(
(puzzle) => PuzzleContext(
angle: const PuzzleTheme(PuzzleThemeKey.mix),
Expand Down Expand Up @@ -332,7 +332,7 @@ class PuzzleController extends _$PuzzleController {
final soundService = ref.read(soundServiceProvider);

if (state.streak == null) {
final next = await ref.withAuthClient(
final next = await ref.withClient(
(client) => _service(client).solve(
userId: initialContext.userId,
angle: initialContext.angle,
Expand Down
Loading

0 comments on commit 76038e6

Please sign in to comment.