fix(mobile): reconnect after backward clock jumps

Co-authored-by: Tom Brow <tomb@block.xyz>
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp
2026-08-02 19:56:19 -07:00
parent 71dbcdb8b1
commit 0f442a8f3b
2 changed files with 63 additions and 2 deletions

View File

@@ -422,9 +422,13 @@ class RelaySessionNotifier extends Notifier<SessionState> {
_backgroundGraceTimer?.cancel();
_backgroundGraceTimer = null;
final backgroundedDuration = backgroundedAt == null
? null
: _now().difference(backgroundedAt);
final backgroundedLongEnoughToRequireReconnect =
backgroundedAt != null &&
_now().difference(backgroundedAt) >= _backgroundGraceDuration;
backgroundedDuration != null &&
(backgroundedDuration.isNegative ||
backgroundedDuration >= _backgroundGraceDuration);
if (!backgroundedLongEnoughToRequireReconnect &&
state.status == SessionStatus.connected) {
return;

View File

@@ -546,6 +546,63 @@ void main() {
},
);
test(
'resume reconnects a stale connected session after a backward clock jump',
() async {
final sockets = <_ControlledRelaySocket>[];
final keychain = nostr.Keys.generate();
var now = DateTime(2026, 8, 2, 12);
final session = RelaySessionNotifier(
now: () => now,
socketFactory:
({
required wsUrl,
required nsec,
required onMessage,
required onConnected,
required onDisconnected,
}) {
final socket = _ControlledRelaySocket(
wsUrl: wsUrl,
nsec: nsec,
onMessage: onMessage,
onConnected: onConnected,
onDisconnected: onDisconnected,
);
sockets.add(socket);
return socket;
},
);
final container = ProviderContainer(
overrides: [
relaySessionProvider.overrideWith(() => session),
relayConfigProvider.overrideWith(
() => _FakeRelayConfigNotifier(
baseUrl: 'https://relay.example',
nsec: keychain.nsec,
),
),
authProvider.overrideWith(() => _AuthenticatedAuthNotifier()),
],
);
addTearDown(container.dispose);
await container.read(authProvider.future);
final subscription = container.listen(relaySessionProvider, (_, _) {});
addTearDown(subscription.close);
await Future<void>.delayed(Duration.zero);
sockets.single.connectSuccessfully();
session.onAppPaused();
now = now.subtract(const Duration(minutes: 5));
session.onAppResumed();
await Future<void>.delayed(Duration.zero);
expect(sockets, hasLength(2));
expect(sockets.first.disposeCalls, 1);
expect(session.state.status, SessionStatus.reconnecting);
},
);
test('delivers the same live event to each matching subscription', () async {
final session = RelaySessionNotifier();
final firstEvents = <NostrEvent>[];