nostr login fetch details and nip05
This commit is contained in:
@@ -6,10 +6,14 @@ class NostrRelay {
|
||||
/// Whether the relay is currently connected.
|
||||
bool isConnected;
|
||||
|
||||
/// Whether the relay is enabled (should be used).
|
||||
bool isEnabled;
|
||||
|
||||
/// Creates a [NostrRelay] instance.
|
||||
NostrRelay({
|
||||
required this.url,
|
||||
this.isConnected = false,
|
||||
this.isEnabled = true,
|
||||
});
|
||||
|
||||
/// Creates a [NostrRelay] from a URL string.
|
||||
@@ -19,7 +23,7 @@ class NostrRelay {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NostrRelay(url: $url, connected: $isConnected)';
|
||||
return 'NostrRelay(url: $url, connected: $isConnected, enabled: $isEnabled)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import 'package:nostr_tools/nostr_tools.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'models/nostr_keypair.dart';
|
||||
import 'models/nostr_event.dart';
|
||||
import 'models/nostr_relay.dart';
|
||||
@@ -66,6 +67,35 @@ class NostrService {
|
||||
disconnectRelay(relayUrl);
|
||||
}
|
||||
|
||||
/// Enables or disables a relay.
|
||||
///
|
||||
/// [relayUrl] - The URL of the relay to enable/disable.
|
||||
/// [enabled] - Whether the relay should be enabled.
|
||||
void setRelayEnabled(String relayUrl, bool enabled) {
|
||||
final relay = _relays.firstWhere(
|
||||
(r) => r.url == relayUrl,
|
||||
orElse: () => throw NostrException('Relay not found: $relayUrl'),
|
||||
);
|
||||
relay.isEnabled = enabled;
|
||||
|
||||
// If disabling, also disconnect
|
||||
if (!enabled && relay.isConnected) {
|
||||
disconnectRelay(relayUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggles all relays enabled/disabled.
|
||||
///
|
||||
/// [enabled] - Whether all relays should be enabled.
|
||||
void setAllRelaysEnabled(bool enabled) {
|
||||
for (final relay in _relays) {
|
||||
relay.isEnabled = enabled;
|
||||
if (!enabled && relay.isConnected) {
|
||||
disconnectRelay(relay.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the list of configured relays.
|
||||
List<NostrRelay> getRelays() {
|
||||
return List.unmodifiable(_relays);
|
||||
@@ -80,6 +110,15 @@ class NostrService {
|
||||
/// Throws [NostrException] if connection fails.
|
||||
Future<Stream<Map<String, dynamic>>> connectRelay(String relayUrl) async {
|
||||
try {
|
||||
// Check if relay is enabled
|
||||
final relay = _relays.firstWhere(
|
||||
(r) => r.url == relayUrl,
|
||||
orElse: () => NostrRelay.fromUrl(relayUrl),
|
||||
);
|
||||
if (!relay.isEnabled) {
|
||||
throw NostrException('Relay is disabled: $relayUrl');
|
||||
}
|
||||
|
||||
if (_connections.containsKey(relayUrl) && _connections[relayUrl] != null) {
|
||||
// Already connected
|
||||
return _messageControllers[relayUrl]!.stream;
|
||||
@@ -95,11 +134,10 @@ class NostrService {
|
||||
}
|
||||
_connections[relayUrl] = channel;
|
||||
|
||||
final controller = StreamController<Map<String, dynamic>>();
|
||||
final controller = StreamController<Map<String, dynamic>>.broadcast();
|
||||
_messageControllers[relayUrl] = controller;
|
||||
|
||||
// Update relay status
|
||||
final relay = _relays.firstWhere((r) => r.url == relayUrl, orElse: () => NostrRelay.fromUrl(relayUrl));
|
||||
// Update relay status (relay already found above)
|
||||
relay.isConnected = true;
|
||||
|
||||
// Listen for messages
|
||||
@@ -303,7 +341,8 @@ class NostrService {
|
||||
/// Fetches profile from a specific relay.
|
||||
Future<NostrProfile?> _fetchProfileFromRelay(String publicKey, String relayUrl, Duration timeout) async {
|
||||
final channel = _connections[relayUrl];
|
||||
if (channel == null) {
|
||||
final messageController = _messageControllers[relayUrl];
|
||||
if (channel == null || messageController == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -312,7 +351,7 @@ class NostrService {
|
||||
final reqId = 'profile_${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
final completer = Completer<NostrProfile?>();
|
||||
final subscription = _messageControllers[relayUrl]?.stream.listen(
|
||||
final subscription = messageController.stream.listen(
|
||||
(message) {
|
||||
// Message format from connectRelay:
|
||||
// {'type': 'EVENT', 'subscription_id': <id>, 'data': <event_json>}
|
||||
@@ -413,6 +452,118 @@ class NostrService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches preferred relays from a NIP-05 identifier.
|
||||
///
|
||||
/// NIP-05 verification endpoint format: https://<domain>/.well-known/nostr.json?name=<local-part>
|
||||
/// The response can include relay hints in the format:
|
||||
/// {
|
||||
/// "names": { "<local-part>": "<hex-pubkey>" },
|
||||
/// "relays": { "<hex-pubkey>": ["wss://relay1.com", "wss://relay2.com"] }
|
||||
/// }
|
||||
///
|
||||
/// [nip05] - The NIP-05 identifier (e.g., '[email protected]').
|
||||
/// [publicKey] - The public key (hex format) to match against relay hints.
|
||||
///
|
||||
/// Returns a list of preferred relay URLs, or empty list if none found.
|
||||
///
|
||||
/// Throws [NostrException] if fetch fails.
|
||||
Future<List<String>> fetchPreferredRelaysFromNip05(
|
||||
String nip05,
|
||||
String publicKey,
|
||||
) async {
|
||||
try {
|
||||
// Parse NIP-05 identifier (format: local-part@domain)
|
||||
final parts = nip05.split('@');
|
||||
if (parts.length != 2) {
|
||||
throw NostrException('Invalid NIP-05 format: $nip05');
|
||||
}
|
||||
|
||||
final localPart = parts[0];
|
||||
final domain = parts[1];
|
||||
|
||||
// Construct the verification URL
|
||||
final url = Uri.https(domain, '/.well-known/nostr.json', {'name': localPart});
|
||||
|
||||
// Fetch the NIP-05 verification data
|
||||
final response = await http.get(url).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
throw NostrException('Timeout fetching NIP-05 data');
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw NostrException('Failed to fetch NIP-05 data: ${response.statusCode}');
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
|
||||
// Extract relay hints for the public key
|
||||
final relays = data['relays'] as Map<String, dynamic>?;
|
||||
if (relays == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Find relays for the matching public key (case-insensitive)
|
||||
final publicKeyLower = publicKey.toLowerCase();
|
||||
for (final entry in relays.entries) {
|
||||
final keyLower = entry.key.toLowerCase();
|
||||
if (keyLower == publicKeyLower) {
|
||||
final relayList = entry.value;
|
||||
if (relayList is List) {
|
||||
return relayList
|
||||
.map((r) => r.toString())
|
||||
.where((r) => r.isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
if (e is NostrException) {
|
||||
rethrow;
|
||||
}
|
||||
throw NostrException('Failed to fetch preferred relays from NIP-05: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads preferred relays from NIP-05 if available and adds them to the relay list.
|
||||
///
|
||||
/// [nip05] - The NIP-05 identifier (e.g., '[email protected]').
|
||||
/// [publicKey] - The public key (hex format) to match against relay hints.
|
||||
///
|
||||
/// Returns the number of relays added.
|
||||
///
|
||||
/// Throws [NostrException] if fetch fails.
|
||||
Future<int> loadPreferredRelaysFromNip05(
|
||||
String nip05,
|
||||
String publicKey,
|
||||
) async {
|
||||
try {
|
||||
final preferredRelays = await fetchPreferredRelaysFromNip05(nip05, publicKey);
|
||||
|
||||
int addedCount = 0;
|
||||
for (final relayUrl in preferredRelays) {
|
||||
try {
|
||||
addRelay(relayUrl);
|
||||
addedCount++;
|
||||
} catch (e) {
|
||||
// Skip invalid relay URLs
|
||||
debugPrint('Warning: Invalid relay URL from NIP-05: $relayUrl');
|
||||
}
|
||||
}
|
||||
|
||||
return addedCount;
|
||||
} catch (e) {
|
||||
if (e is NostrException) {
|
||||
rethrow;
|
||||
}
|
||||
throw NostrException('Failed to load preferred relays from NIP-05: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes all connections and cleans up resources.
|
||||
void dispose() {
|
||||
for (final relayUrl in _connections.keys.toList()) {
|
||||
|
||||
Reference in New Issue
Block a user