@@ -260,6 +260,120 @@ void main() {
260260 expect (session.state.status, SessionStatus .disconnected);
261261 });
262262
263+ test (
264+ 'resume reconnects a stale connected session after a long pause' ,
265+ () async {
266+ final sockets = < _ControlledRelaySocket > [];
267+ final keychain = nostr.Keys .generate ();
268+ var now = DateTime (2026 , 8 , 2 , 12 );
269+ final session = RelaySessionNotifier (
270+ now: () => now,
271+ socketFactory:
272+ ({
273+ required wsUrl,
274+ required nsec,
275+ required onMessage,
276+ required onConnected,
277+ required onDisconnected,
278+ }) {
279+ final socket = _ControlledRelaySocket (
280+ wsUrl: wsUrl,
281+ nsec: nsec,
282+ onMessage: onMessage,
283+ onConnected: onConnected,
284+ onDisconnected: onDisconnected,
285+ );
286+ sockets.add (socket);
287+ return socket;
288+ },
289+ );
290+ final container = ProviderContainer (
291+ overrides: [
292+ relaySessionProvider.overrideWith (() => session),
293+ relayConfigProvider.overrideWith (
294+ () => _FakeRelayConfigNotifier (
295+ baseUrl: 'https://relay.example' ,
296+ nsec: keychain.nsec,
297+ ),
298+ ),
299+ authProvider.overrideWith (() => _AuthenticatedAuthNotifier ()),
300+ ],
301+ );
302+ addTearDown (container.dispose);
303+ await container.read (authProvider.future);
304+ final subscription = container.listen (relaySessionProvider, (_, _) {});
305+ addTearDown (subscription.close);
306+ await Future <void >.delayed (Duration .zero);
307+ sockets.single.connectSuccessfully ();
308+
309+ session.onAppPaused ();
310+ now = now.add (const Duration (minutes: 5 ));
311+ session.onAppResumed ();
312+ await Future <void >.delayed (Duration .zero);
313+
314+ expect (sockets, hasLength (2 ));
315+ expect (sockets.first.disposeCalls, 1 );
316+ expect (session.state.status, SessionStatus .reconnecting);
317+ },
318+ );
319+
320+ test (
321+ 'resume keeps a connected session within the background grace period' ,
322+ () async {
323+ final sockets = < _ControlledRelaySocket > [];
324+ final keychain = nostr.Keys .generate ();
325+ var now = DateTime (2026 , 8 , 2 , 12 );
326+ final session = RelaySessionNotifier (
327+ now: () => now,
328+ socketFactory:
329+ ({
330+ required wsUrl,
331+ required nsec,
332+ required onMessage,
333+ required onConnected,
334+ required onDisconnected,
335+ }) {
336+ final socket = _ControlledRelaySocket (
337+ wsUrl: wsUrl,
338+ nsec: nsec,
339+ onMessage: onMessage,
340+ onConnected: onConnected,
341+ onDisconnected: onDisconnected,
342+ );
343+ sockets.add (socket);
344+ return socket;
345+ },
346+ );
347+ final container = ProviderContainer (
348+ overrides: [
349+ relaySessionProvider.overrideWith (() => session),
350+ relayConfigProvider.overrideWith (
351+ () => _FakeRelayConfigNotifier (
352+ baseUrl: 'https://relay.example' ,
353+ nsec: keychain.nsec,
354+ ),
355+ ),
356+ authProvider.overrideWith (() => _AuthenticatedAuthNotifier ()),
357+ ],
358+ );
359+ addTearDown (container.dispose);
360+ await container.read (authProvider.future);
361+ final subscription = container.listen (relaySessionProvider, (_, _) {});
362+ addTearDown (subscription.close);
363+ await Future <void >.delayed (Duration .zero);
364+ sockets.single.connectSuccessfully ();
365+
366+ session.onAppPaused ();
367+ now = now.add (const Duration (seconds: 4 ));
368+ session.onAppResumed ();
369+ await Future <void >.delayed (Duration .zero);
370+
371+ expect (sockets, hasLength (1 ));
372+ expect (sockets.single.disposeCalls, 0 );
373+ expect (session.state.status, SessionStatus .connected);
374+ },
375+ );
376+
263377 test ('delivers the same live event to each matching subscription' , () async {
264378 final session = RelaySessionNotifier ();
265379 final firstEvents = < NostrEvent > [];
@@ -402,6 +516,7 @@ class _AuthenticatedAuthNotifier extends AuthNotifier {
402516class _ControlledRelaySocket extends RelaySocket {
403517 final void Function () _connected;
404518 final void Function (Object ? error) _disconnected;
519+ int disposeCalls = 0 ;
405520
406521 _ControlledRelaySocket ({
407522 required super .wsUrl,
@@ -416,7 +531,9 @@ class _ControlledRelaySocket extends RelaySocket {
416531 Future <void > connect () async {}
417532
418533 @override
419- void dispose () {}
534+ void dispose () {
535+ disposeCalls++ ;
536+ }
420537
421538 void connectSuccessfully () => _connected ();
422539
0 commit comments