From 719aaca0220f9fffa1a02cd9bb9fe400164cb6cc Mon Sep 17 00:00:00 2001 From: randogoth Date: Wed, 10 Jun 2026 11:20:20 +0300 Subject: [PATCH] code review --- lib/src/file_actor.dart | 15 ++++++++++--- lib/src/file_download_actor.dart | 6 +++++ lib/src/file_upload_actor.dart | 38 +++++++++++++------------------- lib/src/geo_actor.dart | 6 +++++ lib/src/nip46_signer_actor.dart | 4 +++- lib/src/nostr_account_actor.dart | 11 +++++---- lib/src/nostr_event.dart | 36 ++++++++++++++++++++++++++++++ lib/src/nostr_fetch_actor.dart | 6 +++++ lib/src/nostr_publish_actor.dart | 26 ++++++++++++++-------- lib/src/nostr_signer_actor.dart | 37 +++++++++---------------------- 10 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 lib/src/nostr_event.dart diff --git a/lib/src/file_actor.dart b/lib/src/file_actor.dart index f8cee5a..b27c4ae 100644 --- a/lib/src/file_actor.dart +++ b/lib/src/file_actor.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:actors/actors.dart'; +// =============== COMMANDS =============== + sealed class FileMessage { const FileMessage(); } @@ -28,6 +30,8 @@ final class SaveBinaryFile extends FileMessage { const SaveBinaryFile(this.path, this.bytes); } +// =============== RESULTS =============== + sealed class FileResult { const FileResult(); } @@ -46,15 +50,20 @@ final class BinaryFileLoaded extends FileResult { const BinaryFileLoaded(this.bytes); } +// =============== ACTOR =============== + class FileActor with Handler { @override Future handle(FileMessage message) => switch (message) { - LoadFile(:final path) => File(path).readAsString().then(FileLoaded.new), + LoadFile(:final path) => + File(path).readAsString().then(FileLoaded.new), SaveFile(:final path, :final content) => - File(path).writeAsString(content).then((_) => const FileSaved()), + File(path).writeAsString(content).then((_) => + const FileSaved()), LoadBinaryFile(:final path) => File(path).readAsBytes().then(BinaryFileLoaded.new), SaveBinaryFile(:final path, :final bytes) => - File(path).writeAsBytes(bytes).then((_) => const FileSaved()), + File(path).writeAsBytes(bytes).then((_) => + const FileSaved()), }; } diff --git a/lib/src/file_download_actor.dart b/lib/src/file_download_actor.dart index ce8f494..bd10ca9 100644 --- a/lib/src/file_download_actor.dart +++ b/lib/src/file_download_actor.dart @@ -5,6 +5,8 @@ import 'package:http/http.dart' as http; import 'file_actor.dart'; +// =============== COMMANDS =============== + sealed class FileDownloadMessage { const FileDownloadMessage(); } @@ -15,6 +17,8 @@ final class DownloadFile extends FileDownloadMessage { const DownloadFile({required this.url, required this.savePath}); } +// =============== RESULTS =============== + sealed class FileDownloadResult { const FileDownloadResult(); } @@ -24,6 +28,8 @@ final class FileDownloaded extends FileDownloadResult { const FileDownloaded(this.savePath); } +// =============== ACTOR =============== + class FileDownloadActor with Handler { final FileActor _fileActor; diff --git a/lib/src/file_upload_actor.dart b/lib/src/file_upload_actor.dart index 7df48fb..12b4e7b 100644 --- a/lib/src/file_upload_actor.dart +++ b/lib/src/file_upload_actor.dart @@ -1,13 +1,12 @@ import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; import 'package:actors/actors.dart'; import 'package:bip340/bip340.dart' as bip340; -import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'nip44.dart'; +import 'nostr_event.dart'; import 'relay_connection.dart'; const _kServicePubkey = @@ -16,6 +15,8 @@ const _kDefaultApiBaseUrl = 'https://upload.otherwhere.app'; const _kDefaultRelayUrl = 'wss://relay.otherwhere.app/'; const _kMaxFileBytes = 10 * 1024 * 1024; // 10 MB server-side limit +// =============== COMMANDS =============== + sealed class FileUploadMessage { const FileUploadMessage(); } @@ -30,6 +31,8 @@ final class UploadFile extends FileUploadMessage { const UploadFile({required this.bytes, required this.contentType}); } +// =============== RESULTS =============== + sealed class FileUploadResult { const FileUploadResult(); } @@ -43,6 +46,8 @@ final class FileUploaded extends FileUploadResult { const FileUploaded(this.cdnUrl); } +// =============== ACTOR =============== + class FileUploadActor with Handler { final String _privateKey; final String _publicKey; @@ -150,25 +155,12 @@ class FileUploadActor with Handler { required int kind, required List> tags, required String content, - }) { - final ts = DateTime.now().millisecondsSinceEpoch ~/ 1000; - final ser = jsonEncode([0, _publicKey, ts, kind, tags, content]); - final idBytes = sha256.convert(utf8.encode(ser)).bytes; - final id = - idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); - final rng = Random.secure(); - final aux = List.generate(32, (_) => rng.nextInt(256)) - .map((b) => b.toRadixString(16).padLeft(2, '0')) - .join(); - final sig = bip340.sign(_privateKey, id, aux); - return jsonEncode({ - 'id': id, - 'pubkey': _publicKey, - 'created_at': ts, - 'kind': kind, - 'tags': tags, - 'content': content, - 'sig': sig, - }); - } + }) => + signNostrEvent( + privateKey: _privateKey, + publicKey: _publicKey, + kind: kind, + tags: tags, + content: content, + ); } diff --git a/lib/src/geo_actor.dart b/lib/src/geo_actor.dart index 37c5d8d..316d1de 100644 --- a/lib/src/geo_actor.dart +++ b/lib/src/geo_actor.dart @@ -1,6 +1,8 @@ import 'package:actors/actors.dart'; import 'package:dart_geohash/dart_geohash.dart'; +// =============== COMMANDS =============== + sealed class GeoMessage { const GeoMessage(); } @@ -26,6 +28,8 @@ final class NeighborsOf extends GeoMessage { const NeighborsOf(this.geohash); } +// =============== RESULTS =============== + sealed class GeoResult { const GeoResult(); } @@ -47,6 +51,8 @@ final class NeighborsResult extends GeoResult { const NeighborsResult(this.neighbors); } +// =============== ACTOR =============== + class GeoActor with Handler { final _hasher = GeoHasher(); diff --git a/lib/src/nip46_signer_actor.dart b/lib/src/nip46_signer_actor.dart index fdf4249..79b2266 100644 --- a/lib/src/nip46_signer_actor.dart +++ b/lib/src/nip46_signer_actor.dart @@ -10,6 +10,8 @@ import 'nip44.dart'; import 'nostr_signer_actor.dart'; import 'relay_connection.dart'; +// =============== ACTOR =============== + /// NIP-46 remote signer — drop-in replacement for [NostrSignerActor]. /// /// Accepts a bunker URI (`bunker://?relay=&secret=`), @@ -94,7 +96,7 @@ class Nip46SignerActor with Handler { _pending.clear(); } - // --------------------------------------------------------------------------- + // =============== METHODS =============== Future _ensureConnected() async { if (_connected) return; diff --git a/lib/src/nostr_account_actor.dart b/lib/src/nostr_account_actor.dart index 25ac9e8..0257c6c 100644 --- a/lib/src/nostr_account_actor.dart +++ b/lib/src/nostr_account_actor.dart @@ -10,6 +10,8 @@ import 'bip39.dart'; import 'nostr_fetch_actor.dart'; import 'nostr_publish_actor.dart'; +// =============== PROFILE OBJECT =============== + /// Nostr user profile (NIP-01 kind 0 content fields). class NostrProfile { final String? name; @@ -55,8 +57,7 @@ class NostrProfile { }; } -// --------------------------------------------------------------------------- -// Messages +// =============== COMMANDS =============== sealed class AccountMessage { const AccountMessage(); @@ -113,8 +114,7 @@ final class GetPublicData extends AccountMessage { const GetPublicData(); } -// --------------------------------------------------------------------------- -// Results +// =============== RESULTS =============== sealed class AccountResult { const AccountResult(); @@ -161,8 +161,7 @@ final class PublicData extends AccountResult { const PublicData(this.data); } -// --------------------------------------------------------------------------- -// Actor +// =============== ACTOR =============== /// Manages a Nostr user identity: key lifecycle, BIP-39 mnemonic support, and /// profile (kind 0) fetch / publish via injected helper actors. diff --git a/lib/src/nostr_event.dart b/lib/src/nostr_event.dart new file mode 100644 index 0000000..621c745 --- /dev/null +++ b/lib/src/nostr_event.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:bip340/bip340.dart' as bip340; +import 'package:crypto/crypto.dart'; + +String signNostrEvent({ + required String privateKey, + required String publicKey, + required int kind, + required List> tags, + required String content, + int? createdAt, +}) { + final ts = createdAt ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; + final ser = jsonEncode([0, publicKey, ts, kind, tags, content]); + final idBytes = sha256.convert(utf8.encode(ser)).bytes; + final id = idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); + + final rng = Random.secure(); + final aux = List.generate(32, (_) => rng.nextInt(256)) + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(); + + final sig = bip340.sign(privateKey, id, aux); + + return jsonEncode({ + 'id': id, + 'pubkey': publicKey, + 'created_at': ts, + 'kind': kind, + 'tags': tags, + 'content': content, + 'sig': sig, + }); +} diff --git a/lib/src/nostr_fetch_actor.dart b/lib/src/nostr_fetch_actor.dart index ae9119e..a54ad49 100644 --- a/lib/src/nostr_fetch_actor.dart +++ b/lib/src/nostr_fetch_actor.dart @@ -4,6 +4,8 @@ import 'dart:math'; import 'package:actors/actors.dart'; import 'relay_connection.dart'; +// =============== COMMANDS =============== + sealed class FetchMessage { const FetchMessage(); } @@ -13,6 +15,8 @@ final class FetchEvents extends FetchMessage { const FetchEvents(this.filter); } +// =============== RESULTS =============== + sealed class FetchResult { const FetchResult(); } @@ -22,6 +26,8 @@ final class EventsFetched extends FetchResult { const EventsFetched(this.events); } +// =============== ACTOR =============== + class NostrFetchActor with Handler { final List _relayUrls; final Duration _eoseDeadline; diff --git a/lib/src/nostr_publish_actor.dart b/lib/src/nostr_publish_actor.dart index 8631b67..737e8e8 100644 --- a/lib/src/nostr_publish_actor.dart +++ b/lib/src/nostr_publish_actor.dart @@ -4,6 +4,8 @@ import 'package:actors/actors.dart'; import 'package:retry/retry.dart'; import 'relay_connection.dart'; +// =============== HELPER OBJECTS =============== + final class RetryConfig { final int maxAttempts; final Duration baseDelay; @@ -17,6 +19,19 @@ final class RetryConfig { }); } +// =============== COMMANDS =============== + +sealed class PublishMessage { + const PublishMessage(); +} + +final class PublishBatch extends PublishMessage { + final List events; + const PublishBatch(this.events); +} + +// =============== RESULTS =============== + sealed class RelayOutcome { const RelayOutcome(); } @@ -34,15 +49,6 @@ final class RelayFailed extends RelayOutcome { const RelayFailed(); } -sealed class PublishMessage { - const PublishMessage(); -} - -final class PublishBatch extends PublishMessage { - final List events; - const PublishBatch(this.events); -} - sealed class PublishResult { const PublishResult(); } @@ -57,6 +63,8 @@ final class _RelayRejectedException implements Exception { const _RelayRejectedException(this.reason); } +// =============== ACTOR =============== + class NostrPublishActor with Handler { final List _relayUrls; final RetryConfig _retryConfig; diff --git a/lib/src/nostr_signer_actor.dart b/lib/src/nostr_signer_actor.dart index 098d399..9e24b1d 100644 --- a/lib/src/nostr_signer_actor.dart +++ b/lib/src/nostr_signer_actor.dart @@ -1,9 +1,7 @@ -import 'dart:convert'; -import 'dart:math'; - import 'package:actors/actors.dart'; import 'package:bip340/bip340.dart' as bip340; -import 'package:crypto/crypto.dart'; + +import 'nostr_event.dart'; sealed class SignerMessage { const SignerMessage(); @@ -60,26 +58,13 @@ class NostrSignerActor with Handler { List> tags, String content, int? createdAt, - ) { - final ts = createdAt ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; - final ser = jsonEncode([0, _publicKey, ts, kind, tags, content]); - final idBytes = sha256.convert(utf8.encode(ser)).bytes; - final id = idBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); - - final rng = Random.secure(); - final auxBytes = List.generate(32, (_) => rng.nextInt(256)); - final aux = auxBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); - - final sig = bip340.sign(_privateKey, id, aux); - - return EventSigned(jsonEncode({ - 'id': id, - 'pubkey': _publicKey, - 'created_at': ts, - 'kind': kind, - 'tags': tags, - 'content': content, - 'sig': sig, - })); - } + ) => + EventSigned(signNostrEvent( + privateKey: _privateKey, + publicKey: _publicKey, + kind: kind, + tags: tags, + content: content, + createdAt: createdAt, + )); }