fix(mobile): keep rejected activity history unknown

Surface explicit Retry for nonempty history without an acceptable timestamp instead of hiding it as empty. Disable automatic provider retries and cover relay-accepted future timestamps through the activity provider and filter predicate.

Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-10-20 09:30:24 -07:00
parent 844a0fd37e
commit a04934c60f
2 changed files with 34 additions and 5 deletions

View File

@@ -17,6 +17,9 @@ int? verifiedFilterActivity(List<Map<String, dynamic>> records) {
final at = record['created_at'] as int;
if (at <= now + 300 && (latest == null || at > latest)) latest = at;
}
if (records.isNotEmpty && latest == null) {
throw const FormatException('Activity history has no acceptable timestamp');
}
return latest;
}
@@ -91,4 +94,4 @@ final channelFilterActivityProvider = FutureProvider.autoDispose
.toList();
return compute(verifiedFilterActivity, matching);
});
});
}, retry: (_, _) => null);

View File

@@ -18,7 +18,7 @@ void main() {
() async {
final keys = nostr.Keys.generate();
final old = DateTime.now().millisecondsSinceEpoch ~/ 1000 - 8 * 86400;
final event = NostrEvent.fromJson(
NostrEvent activityEvent(int at) => NostrEvent.fromJson(
nostr.Event.from(
kind: EventKind.streamMessage,
tags: [
@@ -26,11 +26,11 @@ void main() {
],
content: 'Earlier activity',
secretKey: nostr.Nip19.decode(payload: keys.nsec).data,
createdAt: old,
createdAt: at,
verify: false,
).toMap(),
);
final session = _ActivitySession(event);
final session = _ActivitySession(activityEvent(old));
final container = ProviderContainer(
overrides: [
channelsProvider.overrideWith(_ActivityChannels.new),
@@ -60,6 +60,32 @@ void main() {
),
isFalse,
);
// A relay-accepted future head must stay unknown, not become known empty.
for (final drift in [450, 600, 900]) {
session.event = activityEvent(
DateTime.now().millisecondsSinceEpoch ~/ 1000 + drift,
);
container.invalidate(channelFilterActivityProvider('quiet-channel'));
await expectLater(
container.read(channelFilterActivityProvider('quiet-channel').future),
throwsFormatException,
);
final evidence = container.read(
channelFilterActivityProvider('quiet-channel'),
);
expect(evidence.hasError, isTrue);
expect(
channelMatchesFilter(
mode: ChannelFilterMode.active,
unread: false,
activitySeconds: evidence.asData?.value,
activityKnown: evidence.asData != null,
nowSeconds: old + 8 * 86400,
windowSeconds: 7 * 86400,
),
isTrue,
);
}
},
);
@@ -358,7 +384,7 @@ class _ActivityChannels extends ChannelsNotifier {
class _ActivitySession extends RelaySessionNotifier {
_ActivitySession(this.event);
final NostrEvent event;
NostrEvent event;
int requests = 0;
@override
SessionState build() => const SessionState(status: SessionStatus.connected);