Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ class MemCacheManager implements CacheManager {
/// In memory storage
Map<String, Metadata> metadatas = {};

/// In memory storage
/// In memory storage indexed by pubKey
Map<String, Nip05> nip05s = {};

/// In memory storage indexed by nip05 identifier
Map<String, Nip05> nip05sByIdentifier = {};

/// In memory storage
Map<String, Nip01Event> events = {};

Expand Down Expand Up @@ -67,11 +70,18 @@ class MemCacheManager implements CacheManager {
@override
Future<void> saveNip05(Nip05 nip05) async {
nip05s[nip05.pubKey] = nip05;
nip05sByIdentifier[nip05.nip05] = nip05;
}

@override
Future<Nip05?> loadNip05(String pubKey) async {
return nip05s[pubKey];
Future<Nip05?> loadNip05({String? pubKey, String? identifier}) async {
if (pubKey != null) {
return nip05s[pubKey];
}
if (identifier != null) {
return nip05sByIdentifier[identifier];
}
return null;
}

@override
Expand All @@ -87,16 +97,22 @@ class MemCacheManager implements CacheManager {
Future<void> saveNip05s(List<Nip05> nip05s) async {
for (var nip05 in nip05s) {
this.nip05s[nip05.pubKey] = nip05;
nip05sByIdentifier[nip05.nip05] = nip05;
}
}

@override
Future<void> removeAllNip05s() async {
nip05s.clear();
nip05sByIdentifier.clear();
}

@override
Future<void> removeNip05(String pubKey) async {
final nip05 = nip05s[pubKey];
if (nip05 != null) {
nip05sByIdentifier.remove(nip05.nip05);
}
nip05s.remove(pubKey);
}

Expand Down
33 changes: 33 additions & 0 deletions packages/ndk/lib/data_layer/repositories/nip_05_http_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,37 @@ class Nip05HttpRepositoryImpl implements Nip05Repository {

return result;
}

@override
Future<Nip05?> fetchNip05(String nip05) async {
String username = nip05.split("@")[0];
String url = nip05.split("@")[1];

String myUrl = "https://$url/.well-known/nostr.json?name=$username";

final json = await httpDS.jsonRequest(myUrl);

Map names = json["names"];
Map relays = json["relays"] ?? {};

// Get pubkey from username or fallback to "_"
String? pubkey = names[username] ?? names["_"];

if (pubkey == null) {
return null;
}

List<String> pRelays = [];
if (relays[pubkey] != null) {
pRelays = List<String>.from(relays[pubkey]);
}

return Nip05Model(
pubKey: pubkey,
nip05: nip05,
valid: true,
networkFetchTime: DateTime.now().millisecondsSinceEpoch ~/ 1000,
relays: pRelays,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ abstract class CacheManager {

Future<void> saveNip05(Nip05 nip05);
Future<void> saveNip05s(List<Nip05> nip05s);
Future<Nip05?> loadNip05(String pubKey);
Future<Nip05?> loadNip05({String? pubKey, String? identifier});
Future<List<Nip05?>> loadNip05s(List<String> pubKeys);
Future<void> removeNip05(String pubKey);
Future<void> removeAllNip05s();
Expand Down
4 changes: 4 additions & 0 deletions packages/ndk/lib/domain_layer/repositories/nip_05_repo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import '../entities/nip_05.dart';
abstract class Nip05Repository {
/// network request to get the Nip05 object
Future<Nip05?> requestNip05(String nip05, String pubkey);

/// fetches NIP-05 data without validation
/// returns pubkey and relays for the given nip05 identifier
Future<Nip05?> fetchNip05(String nip05);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import '../../entities/nip_05.dart';
import '../../repositories/cache_manager.dart';
import '../../repositories/nip_05_repo.dart';

/// usecase to verify the Nip05 object
class VerifyNip05 {
/// usecase to handle Nip05 operations (verify and fetch)
class Nip05Usecase {
// Static map to keep track of in-flight requests
static final Map<String, Future<Nip05>> _inFlightRequests = {};
static final Map<String, Future<Nip05?>> _inFlightFetches = {};

final CacheManager _database;
final Nip05Repository _nip05Repository;

/// creates a new [VerifyNip05] instance
/// creates a new [Nip05Usecase] instance
/// [_database] the cache manager
/// [_nip05Repository] the nip05 repository
VerifyNip05({
Nip05Usecase({
required CacheManager database,
required Nip05Repository nip05Repository,
}) : _database = database,
Expand All @@ -32,7 +33,7 @@ class VerifyNip05 {
throw Exception("nip05 or pubkey empty");
}

final databaseResult = await _database.loadNip05(pubkey);
final databaseResult = await _database.loadNip05(pubKey: pubkey);

if (databaseResult != null) {
int now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
Expand Down Expand Up @@ -80,4 +81,52 @@ class VerifyNip05 {
await _database.saveNip05(result);
return result;
}

/// resolves NIP-05 data without requiring a pubkey for validation
/// returns the [Nip05] object with pubkey and relays
///
/// [nip05] the nip05 identifier (e.g. "username@example.com")
/// returns the [Nip05] object or null if not found
Future<Nip05?> resolve(String nip05) async {
if (nip05.isEmpty) {
throw Exception("nip05 empty");
}

// Check cache first
final databaseResult = await _database.loadNip05(identifier: nip05);
if (databaseResult != null) {
int now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
int lastCheck = databaseResult.networkFetchTime ?? 0;
if (now - lastCheck < NIP_05_VALID_DURATION.inSeconds) {
return databaseResult;
}
}

// Check if there's an in-flight fetch for this nip05
if (_inFlightFetches.containsKey(nip05)) {
return await _inFlightFetches[nip05]!;
}

// Create a new fetch and add it to the in-flight map
final fetch = _performFetch(nip05);
_inFlightFetches[nip05] = fetch;

try {
return await fetch;
} finally {
_inFlightFetches.remove(nip05);
}
}

Future<Nip05?> _performFetch(String nip05) async {
try {
final result = await _nip05Repository.fetchNip05(nip05);
if (result != null) {
await _database.saveNip05(result);
}
return result;
} catch (e) {
return null;
}
}
}
6 changes: 3 additions & 3 deletions packages/ndk/lib/presentation_layer/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import '../domain_layer/usecases/jit_engine/jit_engine.dart';
import '../domain_layer/usecases/lists/lists.dart';
import '../domain_layer/usecases/lnurl/lnurl.dart';
import '../domain_layer/usecases/metadatas/metadatas.dart';
import '../domain_layer/usecases/nip05/verify_nip_05.dart';
import '../domain_layer/usecases/nip05/nip_05.dart';
import '../domain_layer/usecases/nwc/nwc.dart';
import '../domain_layer/usecases/relay_manager.dart';
import '../domain_layer/usecases/relay_sets/relay_sets.dart';
Expand Down Expand Up @@ -84,7 +84,7 @@ class Initialization {
late FetchedRanges fetchedRanges;
late ProofOfWork proofOfWork;

late VerifyNip05 verifyNip05;
late Nip05Usecase nip05;

late final NetworkEngine engine;

Expand Down Expand Up @@ -207,7 +207,7 @@ class Initialization {
blockedRelays: _globalState.blockedRelays,
);

verifyNip05 = VerifyNip05(
nip05 = Nip05Usecase(
database: _ndkConfig.cache,
nip05Repository: nip05repository,
);
Expand Down
6 changes: 3 additions & 3 deletions packages/ndk/lib/presentation_layer/ndk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import '../domain_layer/usecases/follows/follows.dart';
import '../domain_layer/usecases/gift_wrap/gift_wrap.dart';
import '../domain_layer/usecases/lists/lists.dart';
import '../domain_layer/usecases/metadatas/metadatas.dart';
import '../domain_layer/usecases/nip05/verify_nip_05.dart';
import '../domain_layer/usecases/nip05/nip_05.dart';
import '../domain_layer/usecases/nwc/nwc.dart';
import '../domain_layer/usecases/proof_of_work/proof_of_work.dart';
import '../domain_layer/usecases/relay_manager.dart';
Expand Down Expand Up @@ -105,8 +105,8 @@ class Ndk {
/// calculate relay set
RelaySets get relaySets => _initialization.relaySets;

/// Verifies NIP-05 events
VerifyNip05 get nip05 => _initialization.verifyNip05;
/// NIP-05 operations (verify and fetch)
Nip05Usecase get nip05 => _initialization.nip05;

/// manage files on nostr \
/// upload, download, delete files \
Expand Down
2 changes: 1 addition & 1 deletion packages/ndk/lib/src/version.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,22 @@ void main() {
test('saveNip05 and loadNip05', () async {
final mockNip05 = MockNip05();
when(mockNip05.pubKey).thenReturn('testPubKey');
when(mockNip05.nip05).thenReturn('test@example.com');

await cacheManager.saveNip05(mockNip05);
final result = await cacheManager.loadNip05('testPubKey');
final result = await cacheManager.loadNip05(pubKey: 'testPubKey');

expect(result, equals(mockNip05));
});

test('removeNip05', () async {
final mockNip05 = MockNip05();
when(mockNip05.pubKey).thenReturn('testPubKey');
when(mockNip05.nip05).thenReturn('test@example.com');

await cacheManager.saveNip05(mockNip05);
await cacheManager.removeNip05('testPubKey');
final result = await cacheManager.loadNip05('testPubKey');
final result = await cacheManager.loadNip05(pubKey: 'testPubKey');

expect(result, isNull);
});
Expand All @@ -80,13 +82,15 @@ void main() {
final mockNip051 = MockNip05();
final mockNip052 = MockNip05();
when(mockNip051.pubKey).thenReturn('testPubKey1');
when(mockNip051.nip05).thenReturn('test1@example.com');
when(mockNip052.pubKey).thenReturn('testPubKey2');
when(mockNip052.nip05).thenReturn('test2@example.com');

await cacheManager.saveNip05s([mockNip051, mockNip052]);
await cacheManager.removeAllNip05s();

expect(await cacheManager.loadNip05('testPubKey1'), isNull);
expect(await cacheManager.loadNip05('testPubKey2'), isNull);
expect(await cacheManager.loadNip05(pubKey: 'testPubKey1'), isNull);
expect(await cacheManager.loadNip05(pubKey: 'testPubKey2'), isNull);
});
});

Expand Down
Loading