mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(mobile): keep relay sessions stable during push lease updates (#7745)
Enabling push notifications could repeatedly reset the relay session: reserving a lease generation updates community metadata, which rebuilt the relay configuration even though the connection identity was unchanged. This made the avatar and inbox flash and prevented the new lease from publishing. Select only the community ID, relay URL, and signing identity as configuration dependencies. Push bookkeeping keeps the session intact; real community, URL, and identity changes still notify listeners. ### Validation The production-provider regression test failed before the fix and passes afterward; 48 focused mobile tests and static analysis pass. A personal signed profile build on a physical iPhone published a fresh lease and received a background notification with the actual message text. The user confirmed the flashing stopped and tapping the notification opened the correct community/channel and highlighted the exact message. Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -82,12 +82,16 @@ class Env {
|
||||
class RelayConfigNotifier extends Notifier<RelayConfig> {
|
||||
@override
|
||||
RelayConfig build() {
|
||||
// Watch the active community so that when it changes (community switch),
|
||||
// the config rebuilds, triggering the full provider cascade.
|
||||
final activeAsync = ref.watch(activeCommunityProvider);
|
||||
final active = activeAsync.value;
|
||||
if (active != null) {
|
||||
return RelayConfig(baseUrl: active.relayUrl, nsec: active.nsec);
|
||||
// Push lease bookkeeping updates the community while using its live
|
||||
// session. Only connection-context changes may restart that session.
|
||||
final context = ref.watch(
|
||||
activeCommunityProvider.select((value) {
|
||||
final active = value.value;
|
||||
return (id: active?.id, relayUrl: active?.relayUrl, nsec: active?.nsec);
|
||||
}),
|
||||
);
|
||||
if (context.relayUrl != null) {
|
||||
return RelayConfig(baseUrl: context.relayUrl!, nsec: context.nsec);
|
||||
}
|
||||
|
||||
// Fallback to compile-time env config (dev mode).
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:buzz/shared/community/community.dart';
|
||||
import 'package:buzz/shared/community/community_provider.dart';
|
||||
import 'package:buzz/shared/community/community_storage.dart';
|
||||
import 'package:buzz/shared/relay/relay_provider.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'community_storage_test.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'reserving a push lease does not notify relay config listeners',
|
||||
() async {
|
||||
final storage = CommunityStorage(secure: FakeSecureStorage());
|
||||
final community = Community.create(
|
||||
name: 'Tom Town',
|
||||
relayUrl: 'https://relay.example.com',
|
||||
).copyWith(pushNotificationsEnabled: true);
|
||||
await storage.save(community);
|
||||
await storage.saveActiveId(community.id);
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
communityStorageProvider.overrideWithValue(storage),
|
||||
communitySnapshotWriterProvider.overrideWithValue((_) async {}),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
await container.read(activeCommunityProvider.future);
|
||||
final changes = <RelayConfig>[];
|
||||
container.listen(relayConfigProvider, (_, next) => changes.add(next));
|
||||
await container
|
||||
.read(communityListProvider.notifier)
|
||||
.reservePushLeaseGeneration(community.id);
|
||||
await container.read(activeCommunityProvider.future);
|
||||
await container.pump();
|
||||
expect(
|
||||
changes,
|
||||
isEmpty,
|
||||
reason: 'Push metadata must not reset the connected relay session',
|
||||
);
|
||||
},
|
||||
);
|
||||
for (final change in ['community', 'relay', 'identity']) {
|
||||
test('$change change notifies relay config listeners', () async {
|
||||
final storage = CommunityStorage(secure: FakeSecureStorage());
|
||||
final original = Community.create(
|
||||
name: 'Original',
|
||||
relayUrl: 'https://relay.example.com',
|
||||
nsec: 'first',
|
||||
);
|
||||
await storage.save(original);
|
||||
await storage.saveActiveId(original.id);
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
communityStorageProvider.overrideWithValue(storage),
|
||||
communitySnapshotWriterProvider.overrideWithValue((_) async {}),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
await container.read(activeCommunityProvider.future);
|
||||
final changes = <RelayConfig>[];
|
||||
container.listen(relayConfigProvider, (_, next) => changes.add(next));
|
||||
final updated = switch (change) {
|
||||
'community' => Community.create(
|
||||
name: 'Other',
|
||||
relayUrl: original.relayUrl,
|
||||
nsec: original.nsec,
|
||||
),
|
||||
'relay' => original.copyWith(relayUrl: 'https://other.example.com'),
|
||||
_ => original.copyWith(nsec: 'second'),
|
||||
};
|
||||
await storage.save(updated);
|
||||
await storage.saveActiveId(updated.id);
|
||||
container.invalidate(communityListProvider);
|
||||
await container.read(activeCommunityProvider.future);
|
||||
await container.pump();
|
||||
expect(changes, hasLength(1));
|
||||
expect(changes.single.baseUrl, updated.relayUrl);
|
||||
expect(changes.single.nsec, updated.nsec);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user