fix(#472): clear per-radio in-memory caches on reconnect

Switching radios showed the previous radio's channel history (including
its outgoing messages) on the new radio. The in-memory caches
_channelMessages, _conversations, and _loadedConversationKeys are keyed
by channel index / contact key, not by radio, and were never cleared on
a switch; a new radio's empty store could not overwrite them
(_loadChannelMessages only writes on a non-empty read). On-disk stores
are already per-radio (device+PSK since #277), so no re-keying or
migration is needed.

Clear the three caches in _resetConnectionHandshakeState (runs at the
start of every connect). loadAllChannelMessages and _loadMessagesForContact
repopulate from the new radio's store. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/510/head
Strycher 2 months ago
parent 7a2fca929a
commit f84bcaa736

@ -2789,11 +2789,34 @@ class MeshCoreConnector extends ChangeNotifier {
_selfInfoRetryTimer?.cancel();
_selfInfoRetryTimer = null;
_hasReceivedDeviceInfo = false;
// Drop the previous radio's in-memory history before a new connection loads
// its own. These caches are keyed by channel index / contact key, not by
// radio, so without this a radio switch would keep showing the prior
// radio's channel and DM history (a new radio's empty store cannot
// overwrite them). On-disk stores are already per-radio (device+PSK); this
// is purely the runtime cache. Repopulated on connect by
// loadAllChannelMessages and _loadMessagesForContact. (#472)
_channelMessages.clear();
_conversations.clear();
_loadedConversationKeys.clear();
_resetSyncProgressState();
_bleInitialSyncStarted = false;
_pathHashByteWidth = 1;
}
@visibleForTesting
void resetConnectionHandshakeStateForTest() =>
_resetConnectionHandshakeState();
@visibleForTesting
Map<int, List<ChannelMessage>> get channelMessagesForTest => _channelMessages;
@visibleForTesting
Map<String, List<Message>> get conversationsForTest => _conversations;
@visibleForTesting
Set<String> get loadedConversationKeysForTest => _loadedConversationKeys;
void _resetSyncProgressState() {
_pendingInitialChannelSync = false;
_pendingInitialContactsSync = false;

@ -0,0 +1,68 @@
// #472: switching radios must not show the previous radio's history.
//
// The per-radio in-memory caches (_channelMessages, _conversations,
// _loadedConversationKeys) are keyed by channel index / contact key, not by
// radio. _resetConnectionHandshakeState() runs at the start of every
// connection, so it must clear them; otherwise a new radio whose store is empty
// for a channel cannot overwrite the stale entry and keeps rendering the prior
// radio's history (including its outgoing messages). On-disk stores are already
// per-radio (device+PSK), so this is purely the runtime cache.
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/models/channel_message.dart';
import 'package:meshcore_open/storage/prefs_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
SharedPreferences.setMockInitialValues({});
PrefsManager.reset();
await PrefsManager.initialize();
});
test(
'connection reset clears the previous radio in-memory caches (#472)',
() {
final connector = MeshCoreConnector();
// Seed the caches as if a prior radio's history had loaded.
connector.channelMessagesForTest[0] = [
ChannelMessage(
senderName: 'PrevRadio',
text: 'history from the other radio',
timestamp: DateTime.fromMillisecondsSinceEpoch(1000),
isOutgoing: true,
status: ChannelMessageStatus.sent,
),
];
connector.conversationsForTest['deadbeef00'] = [];
connector.loadedConversationKeysForTest.add('deadbeef00');
expect(connector.channelMessagesForTest, isNotEmpty);
expect(connector.conversationsForTest, isNotEmpty);
expect(connector.loadedConversationKeysForTest, isNotEmpty);
// A new connection begins.
connector.resetConnectionHandshakeStateForTest();
expect(
connector.channelMessagesForTest,
isEmpty,
reason: 'channel history cache must be dropped on reconnect',
);
expect(
connector.conversationsForTest,
isEmpty,
reason: 'DM conversation cache must be dropped on reconnect',
);
expect(
connector.loadedConversationKeysForTest,
isEmpty,
reason: 'loaded-conversation markers must be dropped so DMs reload',
);
},
);
}
Loading…
Cancel
Save

Powered by TurnKey Linux.