Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions packages/ndk/lib/domain_layer/usecases/requests/requests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class Requests {

/// Performs a low-level Nostr query
///
/// [filters] A list of filters to apply to the query \
/// [filter] The filter to apply to the query \
/// [filters] @deprecated A list of filters to apply to the query. Use [filter] instead \
/// [name] An optional name used as an ID prefix \
/// [relaySet] An optional set of relays to query \
/// [cacheRead] Whether to read from cache \
Expand All @@ -71,7 +72,9 @@ class Requests {
///
/// Returns an [NdkResponse] containing the query result stream, future
NdkResponse query({
required List<Filter> filters,
Filter? filter,
@Deprecated('Use filter instead. Multiple filters support will be removed in a future version.')
List<Filter>? filters,
String name = '',
RelaySet? relaySet,
bool cacheRead = true,
Expand All @@ -82,12 +85,16 @@ class Requests {
Iterable<String>? explicitRelays,
int? desiredCoverage,
}) {
if (filter == null && (filters == null || filters.isEmpty)) {
throw ArgumentError('Either filter or filters must be provided');
}
final effectiveFilters = filter != null ? [filter] : filters!;
timeout ??= _defaultQueryTimeout;

return requestNostrEvent(NdkRequest.query(
'$name-${Helpers.getRandomString(10)}',
name: name,
filters: filters.map((e) => e.clone()).toList(),
filters: effectiveFilters.map((e) => e.clone()).toList(),
relaySet: relaySet,
cacheRead: cacheRead,
cacheWrite: cacheWrite,
Expand All @@ -102,7 +109,8 @@ class Requests {

/// Creates a low-level Nostr subscription
///
/// [filters] A list of filters to apply to the subscription \
/// [filter] The filter to apply to the subscription \
/// [filters] @deprecated A list of filters to apply to the subscription. Use [filter] instead \
/// [name] An optional name for the subscription \
/// [id] An optional ID for the subscription, overriding name \
/// [relaySet] An optional set of relays to subscribe to \
Expand All @@ -113,7 +121,9 @@ class Requests {
///
/// Returns an [NdkResponse] containing the subscription results as stream
NdkResponse subscription({
required List<Filter> filters,
Filter? filter,
@Deprecated('Use filter instead. Multiple filters support will be removed in a future version.')
List<Filter>? filters,
String name = '',
String? id,
RelaySet? relaySet,
Expand All @@ -122,10 +132,14 @@ class Requests {
Iterable<String>? explicitRelays,
int? desiredCoverage,
}) {
if (filter == null && (filters == null || filters.isEmpty)) {
throw ArgumentError('Either filter or filters must be provided');
}
final effectiveFilters = filter != null ? [filter] : filters!;
return requestNostrEvent(NdkRequest.subscription(
id ?? "$name-${Helpers.getRandomString(10)}",
name: name,
filters: filters.map((e) => e.clone()).toList(),
filters: effectiveFilters.map((e) => e.clone()).toList(),
relaySet: relaySet,
cacheRead: cacheRead,
cacheWrite: cacheWrite,
Expand Down
63 changes: 63 additions & 0 deletions packages/ndk/test/relays/requests_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ void main() async {
};

group('Requests', () {
test('Request text note with single filter parameter', () async {
MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060);
await relay1.startServer(textNotes: textNotes);

final ndk = Ndk(NdkConfig(
eventVerifier: MockEventVerifier(),
cache: MemCacheManager(),
engine: NdkEngine.RELAY_SETS,
bootstrapRelays: [relay1.url],
));
ndk.accounts
.loginPrivateKey(pubkey: key1.publicKey, privkey: key1.privateKey!);

final filter =
Filter(kinds: [Nip01Event.kTextNodeKind], authors: [key1.publicKey]);

// Using the new single filter parameter
final query = ndk.requests.query(filter: filter);

await expectLater(
query.stream, emitsInAnyOrder([textNotes.values.first]));

await ndk.destroy();
expect(ndk.relays.globalState.inFlightRequests.isEmpty, true);
await relay1.stopServer();
});

test('Request text note', () async {
MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060);
await relay1.startServer(textNotes: textNotes);
Expand Down Expand Up @@ -131,6 +158,42 @@ void main() async {
await relay1.stopServer();
});

test('Subscription with single filter parameter', () async {
MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 6060);
await relay1.startServer(textNotes: textNotes);

final ndk = Ndk(NdkConfig(
eventVerifier: MockEventVerifier(),
cache: MemCacheManager(),
engine: NdkEngine.RELAY_SETS,
bootstrapRelays: [relay1.url],
));
ndk.accounts
.loginPrivateKey(pubkey: key1.publicKey, privkey: key1.privateKey!);

final filter =
Filter(kinds: [Nip01Event.kTextNodeKind], authors: [key1.publicKey]);

// Using the new single filter parameter
final subscription = ndk.requests.subscription(filter: filter);

final receivedEvents = <Nip01Event>[];
final streamSubscription = subscription.stream.listen((event) {
receivedEvents.add(event);
});

await Future.delayed(Duration(milliseconds: 200));

expect(receivedEvents.length, equals(1));
expect(receivedEvents[0].content, contains('key1'));

await streamSubscription.cancel();
await ndk.requests.closeSubscription(subscription.requestId);
await ndk.destroy();
expect(ndk.relays.globalState.inFlightRequests.isEmpty, true);
await relay1.stopServer();
});

test('Subscription processes events immediately without stream closing',
() async {
// This test would FAIL with the previous VerifyEventStream implementation
Expand Down