code review
This commit is contained in:
parent
fd3dddf2ed
commit
719aaca022
10 changed files with 117 additions and 68 deletions
|
|
@ -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<FileMessage, FileResult> {
|
||||
@override
|
||||
Future<FileResult> 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()),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FileDownloadMessage, FileDownloadResult> {
|
||||
final FileActor _fileActor;
|
||||
|
|
|
|||
|
|
@ -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<FileUploadMessage, FileUploadResult> {
|
||||
final String _privateKey;
|
||||
final String _publicKey;
|
||||
|
|
@ -150,25 +155,12 @@ class FileUploadActor with Handler<FileUploadMessage, FileUploadResult> {
|
|||
required int kind,
|
||||
required List<List<String>> 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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<GeoMessage, GeoResult> {
|
||||
final _hasher = GeoHasher();
|
||||
|
||||
|
|
|
|||
|
|
@ -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://<signerPubkey>?relay=<url>&secret=<secret>`),
|
||||
|
|
@ -94,7 +96,7 @@ class Nip46SignerActor with Handler<SignerMessage, SignerResult> {
|
|||
_pending.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// =============== METHODS ===============
|
||||
|
||||
Future<void> _ensureConnected() async {
|
||||
if (_connected) return;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
36
lib/src/nostr_event.dart
Normal file
36
lib/src/nostr_event.dart
Normal file
|
|
@ -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<List<String>> 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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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<FetchMessage, FetchResult> {
|
||||
final List<String> _relayUrls;
|
||||
final Duration _eoseDeadline;
|
||||
|
|
|
|||
|
|
@ -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<String> 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<String> 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<PublishMessage, PublishResult> {
|
||||
final List<String> _relayUrls;
|
||||
final RetryConfig _retryConfig;
|
||||
|
|
|
|||
|
|
@ -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<SignerMessage, SignerResult> {
|
|||
List<List<String>> 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,
|
||||
));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue