mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
fix(mobile): read channel metadata and roster separately
findJoinedChannel asked for kinds 39000 and 39002 under one limit of 2. A channel with lingering pre-backfill metadata revisions could fill that limit with metadata and drop the roster, so reopening an older DM would fail after the relay had already opened it. Query each kind with its own limit of 1, in parallel. Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -70,22 +70,31 @@ class ChannelActions {
|
||||
/// and every channel's recent messages. Missing membership fails closed.
|
||||
Future<Channel?> findJoinedChannel(String channelId) async {
|
||||
_ensureCommunityValid();
|
||||
final events = await _session.fetchHistory(
|
||||
NostrFilter(
|
||||
kinds: const [39000, 39002],
|
||||
tags: {
|
||||
'#d': [channelId],
|
||||
},
|
||||
limit: 2,
|
||||
),
|
||||
);
|
||||
// One query per kind: stale kind:39000 revisions from before the relay's
|
||||
// d_tag backfill can linger and would crowd the roster out of a shared
|
||||
// limit.
|
||||
Future<NostrEvent?> latest(int kind) async {
|
||||
final events = await _session.fetchHistory(
|
||||
NostrFilter(
|
||||
kinds: [kind],
|
||||
tags: {
|
||||
'#d': [channelId],
|
||||
},
|
||||
limit: 1,
|
||||
),
|
||||
);
|
||||
return events
|
||||
.where((e) => e.kind == kind && e.getTagValue('d') == channelId)
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
final metadataRequest = latest(39000);
|
||||
final rosterRequest = latest(39002);
|
||||
// Future.wait rethrows the first failure unchanged.
|
||||
await Future.wait(eagerError: true, [metadataRequest, rosterRequest]);
|
||||
final metadata = await metadataRequest;
|
||||
final roster = await rosterRequest;
|
||||
_ensureCommunityValid();
|
||||
final metadata = events
|
||||
.where((e) => e.kind == 39000 && e.getTagValue('d') == channelId)
|
||||
.firstOrNull;
|
||||
final roster = events
|
||||
.where((e) => e.kind == 39002 && e.getTagValue('d') == channelId)
|
||||
.firstOrNull;
|
||||
if (metadata == null || roster == null) return null;
|
||||
final members = membersFromEvent(roster);
|
||||
if (!members.any((m) => m.pubkey == _currentPubkey)) return null;
|
||||
|
||||
@@ -140,42 +140,45 @@ class _DmSession extends RelaySessionNotifier {
|
||||
NostrFilter filter, {
|
||||
Duration timeout = const Duration(seconds: 8),
|
||||
}) async {
|
||||
expect(filter.kinds, [39000, 39002]);
|
||||
expect(filter.tags, {
|
||||
'#d': [_dmId],
|
||||
});
|
||||
onRead?.call();
|
||||
return [
|
||||
NostrEvent(
|
||||
id: 'metadata',
|
||||
pubkey: 'relay',
|
||||
createdAt: 1,
|
||||
kind: 39000,
|
||||
tags: [
|
||||
['d', _dmId],
|
||||
['name', 'DM'],
|
||||
['t', 'dm'],
|
||||
['private'],
|
||||
['hidden'],
|
||||
['p', self],
|
||||
['p', _peer],
|
||||
],
|
||||
content: '',
|
||||
sig: '',
|
||||
),
|
||||
NostrEvent(
|
||||
id: 'roster',
|
||||
pubkey: 'relay',
|
||||
createdAt: 1,
|
||||
kind: 39002,
|
||||
tags: [
|
||||
['d', _dmId],
|
||||
if (member) ['p', self, '', 'member'],
|
||||
['p', _peer, '', 'member'],
|
||||
],
|
||||
content: '',
|
||||
sig: '',
|
||||
),
|
||||
];
|
||||
// Serve like a relay: newest first, one limit across every kind. A stale
|
||||
// metadata revision from before the d_tag backfill still lingers.
|
||||
final events = [
|
||||
_event(39000, 3, [
|
||||
['name', 'DM'],
|
||||
['t', 'dm'],
|
||||
['private'],
|
||||
['hidden'],
|
||||
['p', self],
|
||||
['p', _peer],
|
||||
]),
|
||||
_event(39000, 2, [
|
||||
['name', 'DM'],
|
||||
['t', 'dm'],
|
||||
['private'],
|
||||
]),
|
||||
_event(39002, 1, [
|
||||
if (member) ['p', self, '', 'member'],
|
||||
['p', _peer, '', 'member'],
|
||||
]),
|
||||
].where((e) => filter.kinds.contains(e.kind)).toList();
|
||||
return events.take(filter.limit).toList();
|
||||
}
|
||||
}
|
||||
|
||||
NostrEvent _event(int kind, int createdAt, List<List<String>> tags) =>
|
||||
NostrEvent(
|
||||
id: '$kind-$createdAt',
|
||||
pubkey: 'relay',
|
||||
createdAt: createdAt,
|
||||
kind: kind,
|
||||
tags: [
|
||||
['d', _dmId],
|
||||
...tags,
|
||||
],
|
||||
content: '',
|
||||
sig: '',
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ class _Relay extends RelaySessionNotifier {
|
||||
Future<List<NostrEvent>> fetchHistory(
|
||||
NostrFilter filter, {
|
||||
Duration timeout = const Duration(seconds: 8),
|
||||
}) async => filter.kinds.contains(39000) ? existing : [];
|
||||
}) async => existing.where((e) => filter.kinds.contains(e.kind)).toList();
|
||||
|
||||
@override
|
||||
Future<NostrEvent> publish(
|
||||
|
||||
@@ -144,12 +144,14 @@ class _SoloSession extends RelaySessionNotifier {
|
||||
NostrFilter filter, {
|
||||
Duration timeout = const Duration(seconds: 8),
|
||||
}) async {
|
||||
reads++;
|
||||
expect(filter.kinds, [39000, 39002]);
|
||||
// Each lookup reads metadata and roster separately.
|
||||
final kind = filter.kinds.single;
|
||||
if (kind == 39000) reads++;
|
||||
expect(kind, anyOf(39000, 39002));
|
||||
expect(filter.tags, {
|
||||
'#d': ['solo-id'],
|
||||
});
|
||||
expect(filter.limit, 2);
|
||||
expect(filter.limit, 1);
|
||||
onRead?.call();
|
||||
if (failure != null) throw failure!;
|
||||
if (!exists) return [];
|
||||
@@ -182,6 +184,6 @@ class _SoloSession extends RelaySessionNotifier {
|
||||
content: '',
|
||||
sig: '',
|
||||
),
|
||||
];
|
||||
].where((e) => e.kind == kind).toList();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user