| 
1 | 1 | import 'dart:async';  | 
2 | 2 | 
 
  | 
 | 3 | +import 'package:collection/collection.dart';  | 
3 | 4 | import 'package:dio/dio.dart';  | 
4 | 5 | import 'package:logging/logging.dart';  | 
5 | 6 | import 'package:meta/meta.dart';  | 
@@ -186,9 +187,6 @@ class StreamChatClient {  | 
186 | 187 | 
 
  | 
187 | 188 |   late final RetryPolicy _retryPolicy;  | 
188 | 189 | 
 
  | 
189 |  | -  /// the last dateTime at the which all the channels were synced  | 
190 |  | -  DateTime? _lastSyncedAt;  | 
191 |  | - | 
192 | 190 |   /// The retry policy options getter  | 
193 | 191 |   RetryPolicy get retryPolicy => _retryPolicy;  | 
194 | 192 | 
 
  | 
@@ -519,25 +517,17 @@ class StreamChatClient {  | 
519 | 517 | 
 
  | 
520 | 518 |     if (connectionRecovered) {  | 
521 | 519 |       // connection recovered  | 
522 |  | -      final cids = state.channels.keys.toList(growable: false);  | 
 | 520 | +      final cids = [...state.channels.keys.toSet()];  | 
523 | 521 |       if (cids.isNotEmpty) {  | 
524 | 522 |         await queryChannelsOnline(  | 
525 | 523 |           filter: Filter.in_('cid', cids),  | 
526 | 524 |           paginationParams: const PaginationParams(limit: 30),  | 
527 | 525 |         );  | 
528 |  | -        if (persistenceEnabled) {  | 
529 |  | -          await sync(cids: cids, lastSyncAt: _lastSyncedAt);  | 
530 |  | -        }  | 
531 |  | -      } else {  | 
532 |  | -        // channels are empty, assuming it's a fresh start  | 
533 |  | -        // and making sure `lastSyncAt` is initialized  | 
534 |  | -        if (persistenceEnabled) {  | 
535 |  | -          final lastSyncAt = await chatPersistenceClient?.getLastSyncAt();  | 
536 |  | -          if (lastSyncAt == null) {  | 
537 |  | -            await chatPersistenceClient?.updateLastSyncAt(DateTime.now());  | 
538 |  | -          }  | 
539 |  | -        }  | 
 | 526 | + | 
 | 527 | +        // Sync the persistence client if available  | 
 | 528 | +        if (persistenceEnabled) await sync(cids: cids);  | 
540 | 529 |       }  | 
 | 530 | + | 
541 | 531 |       handleEvent(Event(  | 
542 | 532 |         type: EventType.connectionRecovered,  | 
543 | 533 |         online: true,  | 
@@ -569,34 +559,45 @@ class StreamChatClient {  | 
569 | 559 |   Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) {  | 
570 | 560 |     return _syncLock.synchronized(() async {  | 
571 | 561 |       final channels = cids ?? await chatPersistenceClient?.getChannelCids();  | 
572 |  | -      if (channels == null || channels.isEmpty) {  | 
573 |  | -        return;  | 
574 |  | -      }  | 
 | 562 | +      if (channels == null || channels.isEmpty) return;  | 
575 | 563 | 
 
  | 
576 | 564 |       final syncAt = lastSyncAt ?? await chatPersistenceClient?.getLastSyncAt();  | 
577 | 565 |       if (syncAt == null) {  | 
578 |  | -        return;  | 
 | 566 | +        logger.info('Fresh sync start: lastSyncAt initialized to now.');  | 
 | 567 | +        return chatPersistenceClient?.updateLastSyncAt(DateTime.now());  | 
579 | 568 |       }  | 
580 | 569 | 
 
  | 
581 | 570 |       try {  | 
 | 571 | +        logger.info('Syncing events since $syncAt for channels: $channels');  | 
 | 572 | + | 
582 | 573 |         final res = await _chatApi.general.sync(channels, syncAt);  | 
583 |  | -        final events = res.events  | 
584 |  | -          ..sort((a, b) => a.createdAt.compareTo(b.createdAt));  | 
 | 574 | +        final events = res.events.sorted(  | 
 | 575 | +          (a, b) => a.createdAt.compareTo(b.createdAt),  | 
 | 576 | +        );  | 
585 | 577 | 
 
  | 
586 | 578 |         for (final event in events) {  | 
587 |  | -          logger.fine('event.type: ${event.type}');  | 
588 |  | -          final messageText = event.message?.text;  | 
589 |  | -          if (messageText != null) {  | 
590 |  | -            logger.fine('event.message.text: $messageText');  | 
591 |  | -          }  | 
 | 579 | +          logger.fine('Syncing event: ${event.type}');  | 
592 | 580 |           handleEvent(event);  | 
593 | 581 |         }  | 
594 | 582 | 
 
  | 
595 |  | -        final now = DateTime.now();  | 
596 |  | -        _lastSyncedAt = now;  | 
597 |  | -        chatPersistenceClient?.updateLastSyncAt(now);  | 
598 |  | -      } catch (e, stk) {  | 
599 |  | -        logger.severe('Error during sync', e, stk);  | 
 | 583 | +        final updatedSyncAt = events.lastOrNull?.createdAt ?? DateTime.now();  | 
 | 584 | +        return chatPersistenceClient?.updateLastSyncAt(updatedSyncAt);  | 
 | 585 | +      } catch (error, stk) {  | 
 | 586 | +        // If we got a 400 error, it means that either the sync time is too  | 
 | 587 | +        // old or the channel list is too long or too many events need to be  | 
 | 588 | +        // synced. In this case, we should just flush the persistence client  | 
 | 589 | +        // and start over.  | 
 | 590 | +        if (error is StreamChatNetworkError && error.statusCode == 400) {  | 
 | 591 | +          logger.warning(  | 
 | 592 | +            'Failed to sync events due to stale or oversized state. '  | 
 | 593 | +            'Resetting the persistence client to enable a fresh start.',  | 
 | 594 | +          );  | 
 | 595 | + | 
 | 596 | +          await chatPersistenceClient?.flush();  | 
 | 597 | +          return chatPersistenceClient?.updateLastSyncAt(DateTime.now());  | 
 | 598 | +        }  | 
 | 599 | + | 
 | 600 | +        logger.warning('Error syncing events', error, stk);  | 
600 | 601 |       }  | 
601 | 602 |     });  | 
602 | 603 |   }  | 
@@ -2102,7 +2103,6 @@ class StreamChatClient {  | 
2102 | 2103 |     // resetting state.  | 
2103 | 2104 |     state.dispose();  | 
2104 | 2105 |     state = ClientState(this);  | 
2105 |  | -    _lastSyncedAt = null;  | 
2106 | 2106 | 
 
  | 
2107 | 2107 |     // resetting credentials.  | 
2108 | 2108 |     _tokenManager.reset();  | 
 | 
0 commit comments