api.dart
1 // This file is automatically generated, so please do not edit it. 2 // @generated by `flutter_rust_bridge`@ 2.11.1. 3 4 // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 6 import 'frb_generated.dart'; 7 import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 import 'transport.dart'; 9 import 'transport/dht.dart'; 10 import 'transport/relay.dart'; 11 12 // These functions are ignored because they are not marked as `pub`: `get_db`, `get_dht_transport`, `get_exchanges`, `get_gossip_exchanges`, `get_gossip_peer_keys`, `get_gossip_transports`, `get_handshakes`, `get_iroh_relay_cache`, `get_iroh_transport`, `get_runtime`, `get_transport_preference`, `handle_group_management`, `next_handle`, `process_gossip_messages_for_us`, `queue_delivery_receipt`, `queue_reaction_message`, `search_result_to_info`, `send_message_internal` 13 // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `ExchangeState`, `HandshakeState` 14 // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt` 15 16 /// Initialize the library with storage path and passphrase. 17 /// 18 /// Must be called before any other API functions. 19 /// 20 /// # Arguments 21 /// 22 /// * `storage_path` - Path to the database file 23 /// * `passphrase` - User's passphrase for encryption 24 /// 25 /// # Errors 26 /// 27 /// Returns an error if the database cannot be opened or the passphrase is wrong. 28 void initialize({required String storagePath, required String passphrase}) => 29 RustLib.instance.api.crateApiInitialize( 30 storagePath: storagePath, 31 passphrase: passphrase, 32 ); 33 34 /// Check if the library has been initialized. 35 bool isInitialized() => RustLib.instance.api.crateApiIsInitialized(); 36 37 /// Check if an identity exists. 38 bool hasIdentity() => RustLib.instance.api.crateApiHasIdentity(); 39 40 /// Create a new identity. 41 /// 42 /// Generates new Ed25519 and X25519 key pairs. 43 IdentityInfo createIdentity() => RustLib.instance.api.crateApiCreateIdentity(); 44 45 /// Get the current identity. 46 IdentityInfo getIdentity() => RustLib.instance.api.crateApiGetIdentity(); 47 48 /// Generate QR code payload for sharing identity. 49 String generateQrPayload() => RustLib.instance.api.crateApiGenerateQrPayload(); 50 51 /// Process a scanned QR code. 52 /// 53 /// Returns a pending contact that can be confirmed with `confirm_contact`. 54 PendingContact processQrCode({required String payload}) => 55 RustLib.instance.api.crateApiProcessQrCode(payload: payload); 56 57 /// Confirm a pending contact. 58 /// 59 /// Adds the contact to the database. 60 void confirmContact({required PendingContact pending, String? nickname}) => 61 RustLib.instance.api.crateApiConfirmContact( 62 pending: pending, 63 nickname: nickname, 64 ); 65 66 /// List all contacts. 67 List<ContactInfo> listContacts() => RustLib.instance.api.crateApiListContacts(); 68 69 /// Get a specific contact. 70 ContactInfo getContact({required List<int> contactId}) => 71 RustLib.instance.api.crateApiGetContact(contactId: contactId); 72 73 /// Get a contact's identity public key (ed25519, 32 bytes). 74 /// 75 /// Used for transport-layer authentication when the transport doesn't 76 /// have its own identity handshake (e.g., iroh). 77 Uint8List getContactIdentityKey({required List<int> contactId}) => 78 RustLib.instance.api.crateApiGetContactIdentityKey(contactId: contactId); 79 80 /// Update a contact's nickname. 81 void updateContactNickname({required List<int> contactId, String? nickname}) => 82 RustLib.instance.api.crateApiUpdateContactNickname( 83 contactId: contactId, 84 nickname: nickname, 85 ); 86 87 /// Block a contact. 88 void blockContact({required List<int> contactId}) => 89 RustLib.instance.api.crateApiBlockContact(contactId: contactId); 90 91 /// Unblock a contact. 92 void unblockContact({required List<int> contactId}) => 93 RustLib.instance.api.crateApiUnblockContact(contactId: contactId); 94 95 /// Refresh a contact's public keys from a new QR code payload. 96 /// 97 /// This is used when a contact's identity has changed (e.g., app reinstall) 98 /// and they've shared a new QR code with their updated keys. 99 /// 100 /// # Arguments 101 /// 102 /// * `contact_id` - The existing contact ID to update 103 /// * `qr_payload` - The new QR code payload containing updated keys 104 /// 105 /// # Returns 106 /// 107 /// Ok if keys were updated, Err if validation failed. 108 void refreshContactKeys({ 109 required List<int> contactId, 110 required String qrPayload, 111 }) => RustLib.instance.api.crateApiRefreshContactKeys( 112 contactId: contactId, 113 qrPayload: qrPayload, 114 ); 115 116 /// Delete a contact and all associated messages. 117 void deleteContact({required List<int> contactId}) => 118 RustLib.instance.api.crateApiDeleteContact(contactId: contactId); 119 120 /// Set the disappearing message duration for a contact. 121 /// 122 /// Duration is in seconds. Pass 0 to disable. 123 /// Presets: 300 (5min), 3600 (1hr), 86400 (1 day), 604800 (1 week). 124 void setDisappearingDuration({ 125 required List<int> contactId, 126 required BigInt durationSecs, 127 }) => RustLib.instance.api.crateApiSetDisappearingDuration( 128 contactId: contactId, 129 durationSecs: durationSecs, 130 ); 131 132 /// Get the disappearing message duration for a contact. 133 /// 134 /// Returns duration in seconds (0 = off). 135 BigInt getDisappearingDuration({required List<int> contactId}) => 136 RustLib.instance.api.crateApiGetDisappearingDuration(contactId: contactId); 137 138 /// Set the muted state for a contact's notifications. 139 void setContactMuted({required List<int> contactId, required bool muted}) => 140 RustLib.instance.api.crateApiSetContactMuted( 141 contactId: contactId, 142 muted: muted, 143 ); 144 145 /// Get the muted state for a contact's notifications. 146 bool getContactMuted({required List<int> contactId}) => 147 RustLib.instance.api.crateApiGetContactMuted(contactId: contactId); 148 149 /// Compose and queue a message for delivery. 150 /// 151 /// Returns the message ID. 152 /// Note: Async to avoid blocking UI during encryption of large messages. 153 Future<Uint8List> sendMessage({ 154 required List<int> recipientId, 155 required List<int> content, 156 required int contentType, 157 String? filename, 158 int? ttlDays, 159 }) => RustLib.instance.api.crateApiSendMessage( 160 recipientId: recipientId, 161 content: content, 162 contentType: contentType, 163 filename: filename, 164 ttlDays: ttlDays, 165 ); 166 167 /// Send a document, automatically chunking if larger than MAX_CHUNK_SIZE. 168 /// 169 /// For small files (<= 30KB), sends as a single Document message. 170 /// For large files (> 30KB), splits into DocumentChunk messages. 171 /// 172 /// Returns the document ID (for single messages, this is the message ID; 173 /// for chunked documents, this is the shared document_id across all chunks). 174 Future<Uint8List> sendDocument({ 175 required List<int> recipientId, 176 required List<int> content, 177 required String filename, 178 int? ttlDays, 179 }) => RustLib.instance.api.crateApiSendDocument( 180 recipientId: recipientId, 181 content: content, 182 filename: filename, 183 ttlDays: ttlDays, 184 ); 185 186 /// Reassemble a chunked document from received chunks. 187 /// 188 /// Returns the document content and filename if all chunks are present. 189 /// Returns an error if the document is incomplete. 190 DocumentInfo reassembleDocument({required List<int> documentId}) => 191 RustLib.instance.api.crateApiReassembleDocument(documentId: documentId); 192 193 /// Check if all chunks have been received for a document. 194 bool isDocumentComplete({required List<int> documentId}) => 195 RustLib.instance.api.crateApiIsDocumentComplete(documentId: documentId); 196 197 /// Get document info (filename and size) without reassembling. 198 (String, BigInt)? getDocumentInfo({required List<int> documentId}) => 199 RustLib.instance.api.crateApiGetDocumentInfo(documentId: documentId); 200 201 /// Get outbound message queue for a contact. 202 List<MessageInfo> getOutboundQueue({required List<int> contactId}) => 203 RustLib.instance.api.crateApiGetOutboundQueue(contactId: contactId); 204 205 /// Get messages from a contact (inbound). 206 List<MessageInfo> getMessages({required List<int> contactId}) => 207 RustLib.instance.api.crateApiGetMessages(contactId: contactId); 208 209 /// Get conversation (both inbound and outbound) with a contact. 210 List<MessageInfo> getConversation({required List<int> contactId}) => 211 RustLib.instance.api.crateApiGetConversation(contactId: contactId); 212 213 /// Mark a message as read. 214 void markRead({required List<int> messageId}) => 215 RustLib.instance.api.crateApiMarkRead(messageId: messageId); 216 217 /// Delete a message. 218 void deleteMessage({required List<int> messageId}) => 219 RustLib.instance.api.crateApiDeleteMessage(messageId: messageId); 220 221 /// Clear all messages for a contact without deleting the contact. 222 int clearConversation({required List<int> contactId}) => 223 RustLib.instance.api.crateApiClearConversation(contactId: contactId); 224 225 /// Delete all user data from the database (contacts, messages, identity, groups, etc). 226 void deleteAllData() => RustLib.instance.api.crateApiDeleteAllData(); 227 228 /// Get count of unread messages. 229 int getUnreadCount() => RustLib.instance.api.crateApiGetUnreadCount(); 230 231 /// Retry sending a failed message. 232 /// 233 /// Requeues the message for transmission. 234 void retryMessage({required List<int> messageId}) => 235 RustLib.instance.api.crateApiRetryMessage(messageId: messageId); 236 237 /// Retry messages stuck in Transmitting state by resetting to Queued. 238 /// 239 /// Messages stuck for longer than `timeout_secs` are retried (max 5 attempts). 240 /// Returns the number of messages retried. 241 int retryStuckMessages({required PlatformInt64 timeoutSecs}) => 242 RustLib.instance.api.crateApiRetryStuckMessages(timeoutSecs: timeoutSecs); 243 244 /// Expire stale outbound messages stuck in Queued or Transmitting state. 245 /// 246 /// Messages older than `max_age_secs` are marked as Failed. 247 /// Returns the number of expired messages. 248 int expireStaleMessages({required PlatformInt64 maxAgeSecs}) => 249 RustLib.instance.api.crateApiExpireStaleMessages(maxAgeSecs: maxAgeSecs); 250 251 /// Promote outbound messages stuck in Queued to gossip relay. 252 /// 253 /// Messages that have been Queued for longer than `min_age_secs` are 254 /// ensured to exist in the forwarding store and transitioned to PendingRelay. 255 /// Returns the number of messages promoted. 256 int promoteToGossipRelay({required PlatformInt64 minAgeSecs}) => 257 RustLib.instance.api.crateApiPromoteToGossipRelay(minAgeSecs: minAgeSecs); 258 259 /// Expire PendingRelay messages older than `max_age_secs` (e.g. 48h). 260 /// These have exceeded the forwarding store TTL and will never be delivered. 261 /// Returns the number of messages marked Failed. 262 int expirePendingRelay({required PlatformInt64 maxAgeSecs}) => 263 RustLib.instance.api.crateApiExpirePendingRelay(maxAgeSecs: maxAgeSecs); 264 265 /// Create a ForwardedMessage from an outbound message that failed direct delivery. 266 /// Sets the outbound state to PendingRelay so it's deliverable via BLE gossip mesh. 267 bool queueForGossipRelay({required List<int> messageId}) => 268 RustLib.instance.api.crateApiQueueForGossipRelay(messageId: messageId); 269 270 /// Add a reaction to a message. 271 /// 272 /// Returns the reaction ID. 273 void addReaction({required List<int> messageId, required String emoji}) => 274 RustLib.instance.api.crateApiAddReaction( 275 messageId: messageId, 276 emoji: emoji, 277 ); 278 279 /// Remove a reaction from a message. 280 void removeReaction({required List<int> messageId, required String emoji}) => 281 RustLib.instance.api.crateApiRemoveReaction( 282 messageId: messageId, 283 emoji: emoji, 284 ); 285 286 /// Toggle a reaction on a message (add if not present, remove if present). 287 bool toggleReaction({required List<int> messageId, required String emoji}) => 288 RustLib.instance.api.crateApiToggleReaction( 289 messageId: messageId, 290 emoji: emoji, 291 ); 292 293 /// Get transport delivery statistics. 294 TransportStatsInfo getTransportStats() => 295 RustLib.instance.api.crateApiGetTransportStats(); 296 297 /// Get send progress for a chunked document. 298 /// 299 /// Returns (delivered_chunks, total_chunks) or None if not a chunked document. 300 (int, int)? getSendProgress({required List<int> documentId}) => 301 RustLib.instance.api.crateApiGetSendProgress(documentId: documentId); 302 303 /// Get receive progress for a chunked document. 304 /// 305 /// Returns (received_chunks, total_chunks) or None if not found. 306 (int, int)? getReceiveProgress({required List<int> documentId}) => 307 RustLib.instance.api.crateApiGetReceiveProgress(documentId: documentId); 308 309 /// Search messages within a conversation. 310 /// 311 /// Returns messages matching the query string (case-insensitive). 312 List<SearchResultInfo> searchConversation({ 313 required List<int> contactId, 314 required String query, 315 required int limit, 316 }) => RustLib.instance.api.crateApiSearchConversation( 317 contactId: contactId, 318 query: query, 319 limit: limit, 320 ); 321 322 /// Search all messages across all conversations. 323 /// 324 /// Returns messages matching the query string (case-insensitive). 325 List<SearchResultInfo> searchAllMessages({ 326 required String query, 327 required int limit, 328 }) => 329 RustLib.instance.api.crateApiSearchAllMessages(query: query, limit: limit); 330 331 /// Get my current rotating IDs for BLE advertising. 332 /// 333 /// Returns a list of rotating IDs, one for each active contact. 334 /// Each ID is 16 bytes. Advertise all of these for contacts to discover you. 335 List<Uint8List> getMyRotatingIds() => 336 RustLib.instance.api.crateApiGetMyRotatingIds(); 337 338 /// Check if a scanned rotating ID matches any contact. 339 /// 340 /// Returns the contact ID if found, None otherwise. 341 Uint8List? checkRotatingId({required List<int> scannedId}) => 342 RustLib.instance.api.crateApiCheckRotatingId(scannedId: scannedId); 343 344 /// Create a handshake initiator for connecting to a contact. 345 HandshakeHandle createHandshakeInitiator({required List<int> contactId}) => 346 RustLib.instance.api.crateApiCreateHandshakeInitiator(contactId: contactId); 347 348 /// Create an anonymous handshake initiator for gossip-only connections. 349 /// 350 /// Unlike `create_handshake_initiator`, this does not require a contact ID. 351 /// Used for gossip relay with non-contact Dead Drop peers. 352 HandshakeHandle createHandshakeInitiatorAnonymous() => 353 RustLib.instance.api.crateApiCreateHandshakeInitiatorAnonymous(); 354 355 /// Create a handshake responder for incoming connections. 356 HandshakeHandle createHandshakeResponder() => 357 RustLib.instance.api.crateApiCreateHandshakeResponder(); 358 359 /// Generate the next handshake message to send. 360 Uint8List handshakeGenerate({required HandshakeHandle handle}) => 361 RustLib.instance.api.crateApiHandshakeGenerate(handle: handle); 362 363 /// Process a received handshake message. 364 HandshakeResult handshakeProcess({ 365 required HandshakeHandle handle, 366 required List<int> data, 367 }) => RustLib.instance.api.crateApiHandshakeProcess(handle: handle, data: data); 368 369 /// Finalize a completed handshake into an exchange session. 370 ExchangeHandle handshakeFinalize({required HandshakeHandle handle}) => 371 RustLib.instance.api.crateApiHandshakeFinalize(handle: handle); 372 373 /// Generate exchange data to send. 374 Uint8List? exchangeSend({required ExchangeHandle handle}) => 375 RustLib.instance.api.crateApiExchangeSend(handle: handle); 376 377 /// Process received exchange data. 378 ExchangeResultDto exchangeReceive({ 379 required ExchangeHandle handle, 380 required List<int> data, 381 }) => RustLib.instance.api.crateApiExchangeReceive(handle: handle, data: data); 382 383 /// Finalize an exchange and get results. 384 ExchangeComplete exchangeFinalize({required ExchangeHandle handle}) => 385 RustLib.instance.api.crateApiExchangeFinalize(handle: handle); 386 387 /// Clean up a handshake handle without finalizing. 388 void handshakeCancel({required HandshakeHandle handle}) => 389 RustLib.instance.api.crateApiHandshakeCancel(handle: handle); 390 391 /// Clean up an exchange handle without finalizing. 392 void exchangeCancel({required ExchangeHandle handle}) => 393 RustLib.instance.api.crateApiExchangeCancel(handle: handle); 394 395 /// Finalize a direct exchange AND create a gossip exchange session. 396 /// 397 /// Does everything `exchange_finalize` does (update delivered states, decrypt 398 /// received messages, handle reactions/chunks) PLUS recovers the Noise 399 /// transport and creates a `GossipExchangeOwned` for the gossip phase. 400 (ExchangeComplete, GossipExchangeHandle) exchangeFinalizeForGossip({ 401 required ExchangeHandle handle, 402 }) => RustLib.instance.api.crateApiExchangeFinalizeForGossip(handle: handle); 403 404 /// Get the next gossip data to send over BLE. 405 /// 406 /// Returns encrypted bytes to send, or None if there's nothing to send 407 /// (which means we're waiting for peer data). 408 Uint8List? gossipExchangeSend({required GossipExchangeHandle handle}) => 409 RustLib.instance.api.crateApiGossipExchangeSend(handle: handle); 410 411 /// Process encrypted gossip data received from BLE. 412 bool gossipExchangeReceive({ 413 required GossipExchangeHandle handle, 414 required List<int> data, 415 }) => RustLib.instance.api.crateApiGossipExchangeReceive( 416 handle: handle, 417 data: data, 418 ); 419 420 /// Finalize a gossip exchange and get results. 421 GossipExchangeComplete gossipExchangeFinalize({ 422 required GossipExchangeHandle handle, 423 }) => RustLib.instance.api.crateApiGossipExchangeFinalize(handle: handle); 424 425 /// Cancel a gossip exchange and clean up resources. 426 void gossipExchangeCancel({required GossipExchangeHandle handle}) => 427 RustLib.instance.api.crateApiGossipExchangeCancel(handle: handle); 428 429 /// Finalize a gossip exchange but preserve the Noise transport for reuse. 430 /// 431 /// Returns the exchange results plus a transport handle that can be passed to 432 /// `gossip_start_new_round()` to create a new gossip round over the same 433 /// encrypted channel. 434 GossipFinalizeKeepTransportResult gossipExchangeFinalizeKeepTransport({ 435 required GossipExchangeHandle handle, 436 }) => RustLib.instance.api.crateApiGossipExchangeFinalizeKeepTransport( 437 handle: handle, 438 ); 439 440 /// Create a new gossip exchange round from a previously stored transport. 441 /// 442 /// Reuses the Noise transport from a prior `gossip_exchange_finalize_keep_transport()` 443 /// call, creating a fresh gossip state machine for a new exchange round. 444 GossipExchangeHandle gossipStartNewRound({required BigInt transportHandle}) => 445 RustLib.instance.api.crateApiGossipStartNewRound( 446 transportHandle: transportHandle, 447 ); 448 449 /// Release a stored gossip transport (cleanup on disconnect). 450 void gossipTransportRelease({required BigInt transportHandle}) => RustLib 451 .instance 452 .api 453 .crateApiGossipTransportRelease(transportHandle: transportHandle); 454 455 /// Finalize a completed handshake directly into a gossip exchange, skipping Exchange. 456 /// 457 /// Used for anonymous gossip-only connections with non-contact peers. 458 /// Extracts the NoiseTransport from the handshake and creates a GossipExchangeOwned 459 /// for store-and-forward relay without requiring a contact relationship. 460 GossipExchangeHandle handshakeFinalizeForGossip({ 461 required HandshakeHandle handle, 462 }) => RustLib.instance.api.crateApiHandshakeFinalizeForGossip(handle: handle); 463 464 /// Delete expired messages and clean up replay cache. 465 /// Returns the total number of messages deleted. 466 int runMaintenance() => RustLib.instance.api.crateApiRunMaintenance(); 467 468 /// Get database statistics. 469 String getStatistics() => RustLib.instance.api.crateApiGetStatistics(); 470 471 /// Get a list of all transport types (available or not). 472 /// 473 /// This returns all transport types that Dead Drop supports, along with 474 /// their availability status on this device. 475 List<TransportInfoDto> getAvailableTransports() => 476 RustLib.instance.api.crateApiGetAvailableTransports(); 477 478 /// Check if a specific transport is available. 479 /// 480 /// # Arguments 481 /// 482 /// * `transport_type` - The transport type to check 483 /// 484 /// # Returns 485 /// 486 /// `true` if the transport is available and initialized. 487 bool isTransportAvailable({required TransportTypeDto transportType}) => RustLib 488 .instance 489 .api 490 .crateApiIsTransportAvailable(transportType: transportType); 491 492 /// Get the human-readable name for a transport type. 493 String getTransportName({required TransportTypeDto transportType}) => 494 RustLib.instance.api.crateApiGetTransportName(transportType: transportType); 495 496 /// Get the current state of the relay transport. 497 /// 498 /// Note: This is a placeholder that returns disconnected state. 499 /// Full relay integration requires transport manager setup. 500 RelayStateDto getRelayState() => RustLib.instance.api.crateApiGetRelayState(); 501 502 /// Get the current state of the DHT transport. 503 DhtStateDto getDhtState() => RustLib.instance.api.crateApiGetDhtState(); 504 505 /// Connect to a relay server. 506 /// 507 /// # Arguments 508 /// 509 /// * `url` - WebSocket URL of the relay server 510 /// 511 /// Note: This is a placeholder. Full implementation requires transport manager. 512 void connectRelay({required String url}) => 513 RustLib.instance.api.crateApiConnectRelay(url: url); 514 515 /// Disconnect from the current relay server. 516 /// 517 /// Note: This is a placeholder. Full implementation requires transport manager. 518 void disconnectRelay() => RustLib.instance.api.crateApiDisconnectRelay(); 519 520 /// Bootstrap the DHT with a list of nodes. 521 /// 522 /// # Arguments 523 /// 524 /// * `nodes` - List of bootstrap node addresses (host:port format) 525 /// 526 /// Example nodes: 527 /// - `router.bittorrent.com:6881` 528 /// - `router.utorrent.com:6881` 529 /// - `dht.transmissionbt.com:6881` 530 /// 531 /// Note: This is async to avoid blocking the UI thread. 532 Future<void> bootstrapDht({required List<String> nodes}) => 533 RustLib.instance.api.crateApiBootstrapDht(nodes: nodes); 534 535 /// Disconnect from the DHT network. 536 /// 537 /// Note: This is async to avoid blocking the UI thread. 538 Future<void> disconnectDht() => RustLib.instance.api.crateApiDisconnectDht(); 539 540 /// Set the network transport preference. 541 /// 542 /// This controls which transport is preferred when multiple are available. 543 /// 544 /// # Arguments 545 /// 546 /// * `preference` - The transport preference to use 547 void setTransportPreference({required NetworkTransportPreference preference}) => 548 RustLib.instance.api.crateApiSetTransportPreference(preference: preference); 549 550 /// Get the current network transport preference. 551 NetworkTransportPreference getTransportPreferenceValue() => 552 RustLib.instance.api.crateApiGetTransportPreferenceValue(); 553 554 /// Publish queued messages to the DHT for a contact. 555 /// 556 /// This takes all queued messages for the specified contact, packages them 557 /// into DHT-storable format, and publishes them to the DHT network. 558 /// 559 /// # Arguments 560 /// 561 /// * `contact_id` - The contact to publish messages for 562 /// 563 /// # Returns 564 /// 565 /// The number of messages published and their IDs. 566 /// Note: Truly async to avoid blocking UI - DB ops run on blocking thread, 567 /// network ops run async. 568 Future<DhtPublishResult> publishMessagesToDht({required List<int> contactId}) => 569 RustLib.instance.api.crateApiPublishMessagesToDht(contactId: contactId); 570 571 /// Fetch messages from the DHT for our public key. 572 /// 573 /// This queries the DHT for messages stored for our public key, 574 /// decrypts and validates them, and stores them as inbound messages. 575 /// 576 /// # Returns 577 /// 578 /// Messages and contacts with reaction updates. 579 /// Note: Truly async to avoid blocking UI - phases structured to minimize lock time. 580 Future<DhtFetchResult> fetchMessagesFromDht() => 581 RustLib.instance.api.crateApiFetchMessagesFromDht(); 582 583 /// Acknowledge DHT messages (mark as received). 584 /// 585 /// After successfully storing messages locally, call this to remove 586 /// them from the DHT to prevent re-fetching. 587 /// 588 /// # Arguments 589 /// 590 /// * `message_ids` - IDs of messages to acknowledge 591 void acknowledgeDhtMessages({required List<Uint8List> messageIds}) => 592 RustLib.instance.api.crateApiAcknowledgeDhtMessages(messageIds: messageIds); 593 594 /// Get queued encrypted messages for a contact, ready for Tor P2P delivery. 595 /// 596 /// Returns serialized encrypted payloads that can be sent directly over 597 /// a WebSocket connection. The messages remain queued until explicitly 598 /// marked as delivered via [mark_delivered_via_tor]. 599 Future<List<EncryptedPayload>> getQueuedEncrypted({ 600 required List<int> contactId, 601 }) => RustLib.instance.api.crateApiGetQueuedEncrypted(contactId: contactId); 602 603 /// Re-queue any Failed messages for a contact so they can be retried. 604 /// 605 /// Resets OutboundState::Failed back to OutboundState::Queued. 606 Future<int> requeueFailedMessages({required List<int> contactId}) => 607 RustLib.instance.api.crateApiRequeueFailedMessages(contactId: contactId); 608 609 /// Mark messages as delivered via Tor P2P transport. 610 /// 611 /// Call after successfully sending messages over a WebSocket connection 612 /// to update their delivery status. 613 Future<void> markDeliveredViaTor({required List<Uint8List> messageIds}) => 614 RustLib.instance.api.crateApiMarkDeliveredViaTor(messageIds: messageIds); 615 616 /// Set the delivery transport for outbound messages (only if not already set). 617 /// 618 /// Used to record which transport sent the message, even when the transport 619 /// doesn't confirm delivery (e.g. iroh relay). 620 Future<void> setOutboundTransport({ 621 required List<Uint8List> messageIds, 622 required String transport, 623 }) => RustLib.instance.api.crateApiSetOutboundTransport( 624 messageIds: messageIds, 625 transport: transport, 626 ); 627 628 /// Process an encrypted message received via Tor P2P. 629 /// 630 /// Decrypts and stores the message, similar to how DHT-received messages 631 /// are processed. Returns the message info if successfully processed. 632 /// Look up a contact's ID by their identity public key (ed25519). 633 /// 634 /// Used to map P2P server connections (which provide identity keys during 635 /// handshake) back to contact IDs for bidirectional messaging. 636 Future<Uint8List?> getContactIdByIdentityKey({ 637 required List<int> identityKey, 638 }) => RustLib.instance.api.crateApiGetContactIdByIdentityKey( 639 identityKey: identityKey, 640 ); 641 642 Future<MessageInfo?> processTorMessage({ 643 required List<int> senderIdentityKey, 644 required List<int> payload, 645 }) => RustLib.instance.api.crateApiProcessTorMessage( 646 senderIdentityKey: senderIdentityKey, 647 payload: payload, 648 ); 649 650 /// Publish our .onion address to the DHT for peer discovery. 651 /// 652 /// Other peers who know our public key can fetch this address to establish 653 /// direct Tor P2P connections. Re-publish periodically as addresses may change. 654 Future<void> publishOnionAddress({required String onionAddress}) => RustLib 655 .instance 656 .api 657 .crateApiPublishOnionAddress(onionAddress: onionAddress); 658 659 /// Fetch a contact's .onion address from the DHT. 660 /// 661 /// Returns the onion address if the contact has published one recently 662 /// (within 1 hour). Returns None if not found or stale. 663 Future<String?> fetchContactOnionAddress({required List<int> contactId}) => 664 RustLib.instance.api.crateApiFetchContactOnionAddress(contactId: contactId); 665 666 /// Get all cached onion addresses for contacts. 667 /// 668 /// Returns a list of (contact_id_hex, onion_address) pairs for contacts 669 /// that have a previously discovered onion address. Used on startup to 670 /// enable instant Tor P2P connections without waiting for DHT rendezvous. 671 Future<List<CachedOnionAddress>> getCachedOnionAddresses() => 672 RustLib.instance.api.crateApiGetCachedOnionAddresses(); 673 674 /// Get the current reputation score and level. 675 ReputationInfo getReputation() => RustLib.instance.api.crateApiGetReputation(); 676 677 /// Get all achievements with their unlock status. 678 List<AchievementInfo> getAchievements() => 679 RustLib.instance.api.crateApiGetAchievements(); 680 681 /// Get forwarding statistics. 682 ForwardingInfo getForwardingStats() => 683 RustLib.instance.api.crateApiGetForwardingStats(); 684 685 /// Record that we forwarded a message (for reputation tracking). 686 /// 687 /// Call this after successfully storing a message for forwarding. 688 List<String> recordMessageForwarded({required List<int> recipientKeyHash}) => 689 RustLib.instance.api.crateApiRecordMessageForwarded( 690 recipientKeyHash: recipientKeyHash, 691 ); 692 693 /// Record a delivery confirmation (for reputation tracking). 694 /// 695 /// Call this when we receive confirmation that a message was delivered. 696 List<String> recordDeliveryConfirmation({required List<int> messageHash}) => 697 RustLib.instance.api.crateApiRecordDeliveryConfirmation( 698 messageHash: messageHash, 699 ); 700 701 /// Cleanup expired forwarded messages. 702 /// 703 /// Returns the number of messages deleted. 704 int cleanupForwardedMessages() => 705 RustLib.instance.api.crateApiCleanupForwardedMessages(); 706 707 /// Clear all forwarded messages from the relay store. 708 /// 709 /// Used to reset stale relay data. Returns the number of messages deleted. 710 int clearForwardedMessages() => 711 RustLib.instance.api.crateApiClearForwardedMessages(); 712 713 /// Enforce forwarding storage limit. 714 /// 715 /// Deletes oldest messages until under the limit. 716 /// Returns the number of messages deleted. 717 int enforceForwardingLimit() => 718 RustLib.instance.api.crateApiEnforceForwardingLimit(); 719 720 /// Generate a gossip digest for BLE mesh exchange. 721 /// 722 /// Returns serialized `GossipDigest` message bytes containing a bloom filter 723 /// of recipient key hashes for messages we're holding for forwarding. 724 Uint8List generateGossipDigest() => 725 RustLib.instance.api.crateApiGenerateGossipDigest(); 726 727 /// Process a peer's gossip digest and generate a request. 728 /// 729 /// Takes serialized `GossipDigest` bytes from a peer. 730 /// Returns serialized `GossipRequest` bytes to send back, or None if 731 /// we're not interested in any of the messages they have. 732 Uint8List? processGossipDigest({required List<int> digestBytes}) => 733 RustLib.instance.api.crateApiProcessGossipDigest(digestBytes: digestBytes); 734 735 /// Handle a gossip request from a peer and generate a response. 736 /// 737 /// Takes serialized `GossipRequest` bytes. 738 /// Returns serialized `GossipResponse` bytes containing matching messages. 739 Uint8List handleGossipRequest({required List<int> requestBytes}) => RustLib 740 .instance 741 .api 742 .crateApiHandleGossipRequest(requestBytes: requestBytes); 743 744 /// Process a gossip response from a peer - store forwarded messages. 745 /// 746 /// Takes serialized `GossipResponse` bytes. 747 /// Returns stats about what was stored. 748 GossipResult processGossipResponse({required List<int> responseBytes}) => 749 RustLib.instance.api.crateApiProcessGossipResponse( 750 responseBytes: responseBytes, 751 ); 752 753 /// Get the current state of the iroh transport. 754 IrohStateDto getIrohState() => RustLib.instance.api.crateApiGetIrohState(); 755 756 /// Start the iroh P2P transport. 757 /// 758 /// Initializes an iroh QUIC endpoint with NAT traversal via n0.computer relays. 759 /// The endpoint accepts incoming connections using the custom Dead Drop ALPN protocol. 760 Future<void> startIroh() => RustLib.instance.api.crateApiStartIroh(); 761 762 /// Stop the iroh P2P transport. 763 Future<void> stopIroh() => RustLib.instance.api.crateApiStopIroh(); 764 765 /// Publish our iroh EndpointId to the DHT for peer discovery. 766 /// 767 /// Other peers who know our public key can fetch this EndpointId to establish 768 /// direct iroh QUIC connections. The published value includes our relay URL 769 /// so peers can connect without relying on iroh's built-in discovery. 770 Future<void> publishIrohEndpointId() => 771 RustLib.instance.api.crateApiPublishIrohEndpointId(); 772 773 /// Fetch a contact's iroh EndpointId from the DHT. 774 /// 775 /// Returns the EndpointId hex string if the contact has published one recently. 776 /// Caches the result in the contacts table for instant reconnect on next startup. 777 Future<String?> fetchContactIrohEndpointId({required List<int> contactId}) => 778 RustLib.instance.api.crateApiFetchContactIrohEndpointId( 779 contactId: contactId, 780 ); 781 782 /// Send an encrypted payload to a peer via iroh QUIC transport. 783 Future<void> sendViaIroh({ 784 required String endpointId, 785 required List<int> payload, 786 }) => RustLib.instance.api.crateApiSendViaIroh( 787 endpointId: endpointId, 788 payload: payload, 789 ); 790 791 /// Manually set a contact's iroh endpoint ID (bypasses DHT discovery). 792 /// 793 /// Useful when DHT propagation is slow and the endpoint ID is known through 794 /// other means (e.g., out-of-band exchange). 795 void setContactIrohEndpoint({ 796 required List<int> contactId, 797 required String endpointId, 798 }) => RustLib.instance.api.crateApiSetContactIrohEndpoint( 799 contactId: contactId, 800 endpointId: endpointId, 801 ); 802 803 /// Process an encrypted message received via iroh P2P. 804 /// 805 /// Decrypts and stores the message, identical to process_tor_message but 806 /// with iroh delivery transport. The sender_identity_key is the ed25519 807 /// public key exchanged during the P2P handshake. 808 Future<MessageInfo?> processIrohMessage({ 809 required List<int> senderIdentityKey, 810 required List<int> payload, 811 }) => RustLib.instance.api.crateApiProcessIrohMessage( 812 senderIdentityKey: senderIdentityKey, 813 payload: payload, 814 ); 815 816 /// Process an iroh message when the sender's endpoint ID is unknown. 817 /// 818 /// Tries all contacts to find which one can decrypt the message. 819 /// Slower than process_iroh_message but handles cases where the peer's 820 /// endpoint ID hasn't been cached/discovered yet (e.g., group invites 821 /// from contacts whose iroh rendezvous hasn't been fetched). 822 Future<MessageInfo?> processIrohMessageUnknownSender({ 823 required List<int> payload, 824 }) => RustLib.instance.api.crateApiProcessIrohMessageUnknownSender( 825 payload: payload, 826 ); 827 828 /// Poll for incoming iroh messages (non-blocking). 829 /// 830 /// Returns the next available message as `(sender_node_id_bytes, payload_bytes)`, 831 /// or None if no messages are waiting. 832 Future<IrohIncomingMessage?> pollIrohIncoming() => 833 RustLib.instance.api.crateApiPollIrohIncoming(); 834 835 /// Get all cached iroh endpoint IDs for contacts. 836 /// 837 /// Returns a list of (contact_id, endpoint_id) pairs for contacts 838 /// that have a previously discovered iroh EndpointId. 839 Future<List<CachedIrohEndpoint>> getCachedIrohEndpointIds() => 840 RustLib.instance.api.crateApiGetCachedIrohEndpointIds(); 841 842 /// Create a new group chat. 843 /// 844 /// Returns the group ID. Sends Invite messages to all members. 845 Future<Uint8List> createGroup({ 846 required String name, 847 required List<Uint8List> memberIds, 848 }) => 849 RustLib.instance.api.crateApiCreateGroup(name: name, memberIds: memberIds); 850 851 /// List all groups with metadata. 852 List<GroupInfo> listGroups() => RustLib.instance.api.crateApiListGroups(); 853 854 /// Get members of a group. 855 List<GroupMemberInfoDto> getGroupMembers({required List<int> groupId}) => 856 RustLib.instance.api.crateApiGetGroupMembers(groupId: groupId); 857 858 /// Send a message to all members of a group (fan-out). 859 Future<Uint8List> sendGroupMessage({ 860 required List<int> groupId, 861 required List<int> content, 862 required int contentType, 863 String? filename, 864 }) => RustLib.instance.api.crateApiSendGroupMessage( 865 groupId: groupId, 866 content: content, 867 contentType: contentType, 868 filename: filename, 869 ); 870 871 /// Send a document to a group, automatically chunking if larger than MAX_CHUNK_SIZE. 872 /// 873 /// Small files (≤30KB) are sent as a single Document message via send_group_message. 874 /// Large files are chunked and each chunk is fan-out to all group members. 875 /// Returns the document ID. 876 Future<Uint8List> sendGroupDocument({ 877 required List<int> groupId, 878 required List<int> content, 879 required String filename, 880 }) => RustLib.instance.api.crateApiSendGroupDocument( 881 groupId: groupId, 882 content: content, 883 filename: filename, 884 ); 885 886 /// Get conversation messages for a group. 887 List<MessageInfo> getGroupConversation({required List<int> groupId}) => 888 RustLib.instance.api.crateApiGetGroupConversation(groupId: groupId); 889 890 /// Add a member to a group and notify existing members. 891 Future<void> addGroupMember({ 892 required List<int> groupId, 893 required List<int> contactId, 894 }) => RustLib.instance.api.crateApiAddGroupMember( 895 groupId: groupId, 896 contactId: contactId, 897 ); 898 899 /// Remove a member from a group and notify remaining members. 900 Future<void> removeGroupMember({ 901 required List<int> groupId, 902 required List<int> contactId, 903 }) => RustLib.instance.api.crateApiRemoveGroupMember( 904 groupId: groupId, 905 contactId: contactId, 906 ); 907 908 /// Leave a group and notify remaining members. 909 Future<void> leaveGroup({required List<int> groupId}) => 910 RustLib.instance.api.crateApiLeaveGroup(groupId: groupId); 911 912 /// Rename a group and notify members. 913 Future<void> renameGroup({ 914 required List<int> groupId, 915 required String newName, 916 }) => RustLib.instance.api.crateApiRenameGroup( 917 groupId: groupId, 918 newName: newName, 919 ); 920 921 /// Set group muted status. 922 void setGroupMuted({required List<int> groupId, required bool isMuted}) => 923 RustLib.instance.api.crateApiSetGroupMuted( 924 groupId: groupId, 925 isMuted: isMuted, 926 ); 927 928 /// Set group disappearing message duration. 929 void setGroupDisappearing({ 930 required List<int> groupId, 931 required BigInt duration, 932 }) => RustLib.instance.api.crateApiSetGroupDisappearing( 933 groupId: groupId, 934 duration: duration, 935 ); 936 937 /// Accept a group invite. 938 void acceptGroupInvite({required List<int> groupId}) => 939 RustLib.instance.api.crateApiAcceptGroupInvite(groupId: groupId); 940 941 /// Decline a group invite – notify members with a Leave action, then delete locally. 942 Future<void> declineGroupInvite({required List<int> groupId}) => 943 RustLib.instance.api.crateApiDeclineGroupInvite(groupId: groupId); 944 945 /// Check if a group invite has been accepted. 946 bool isGroupAccepted({required List<int> groupId}) => 947 RustLib.instance.api.crateApiIsGroupAccepted(groupId: groupId); 948 949 /// DTO for achievement information. 950 class AchievementInfo { 951 /// Achievement ID. 952 final int id; 953 954 /// Display name. 955 final String name; 956 957 /// Description. 958 final String description; 959 960 /// Whether unlocked. 961 final bool unlocked; 962 963 const AchievementInfo({ 964 required this.id, 965 required this.name, 966 required this.description, 967 required this.unlocked, 968 }); 969 970 @override 971 int get hashCode => 972 id.hashCode ^ name.hashCode ^ description.hashCode ^ unlocked.hashCode; 973 974 @override 975 bool operator ==(Object other) => 976 identical(this, other) || 977 other is AchievementInfo && 978 runtimeType == other.runtimeType && 979 id == other.id && 980 name == other.name && 981 description == other.description && 982 unlocked == other.unlocked; 983 } 984 985 /// DTO for a cached iroh endpoint ID. 986 class CachedIrohEndpoint { 987 final Uint8List contactId; 988 final String endpointId; 989 990 const CachedIrohEndpoint({required this.contactId, required this.endpointId}); 991 992 @override 993 int get hashCode => contactId.hashCode ^ endpointId.hashCode; 994 995 @override 996 bool operator ==(Object other) => 997 identical(this, other) || 998 other is CachedIrohEndpoint && 999 runtimeType == other.runtimeType && 1000 contactId == other.contactId && 1001 endpointId == other.endpointId; 1002 } 1003 1004 /// DTO for cached onion address. 1005 class CachedOnionAddress { 1006 final Uint8List contactId; 1007 final String onionAddress; 1008 1009 const CachedOnionAddress({ 1010 required this.contactId, 1011 required this.onionAddress, 1012 }); 1013 1014 @override 1015 int get hashCode => contactId.hashCode ^ onionAddress.hashCode; 1016 1017 @override 1018 bool operator ==(Object other) => 1019 identical(this, other) || 1020 other is CachedOnionAddress && 1021 runtimeType == other.runtimeType && 1022 contactId == other.contactId && 1023 onionAddress == other.onionAddress; 1024 } 1025 1026 /// Contact information. 1027 class ContactInfo { 1028 /// Unique contact identifier. 1029 final Uint8List contactId; 1030 1031 /// User-assigned nickname. 1032 final String? nickname; 1033 1034 /// Contact state (0=Pending, 1=Active, 2=Blocked, 3=Revoked). 1035 final int state; 1036 1037 /// Human-readable fingerprint. 1038 final String fingerprint; 1039 1040 /// Creation timestamp (Unix seconds). 1041 final PlatformInt64 createdAt; 1042 1043 /// Last seen timestamp (Unix seconds). 1044 final PlatformInt64? lastSeen; 1045 1046 /// Number of queued outbound messages. 1047 final int queuedCount; 1048 1049 /// Number of unread inbound messages. 1050 final int unreadCount; 1051 1052 /// Disappearing message duration in seconds (0 = off). 1053 final BigInt disappearingDuration; 1054 1055 /// Whether notifications are muted for this contact. 1056 final bool isMuted; 1057 1058 const ContactInfo({ 1059 required this.contactId, 1060 this.nickname, 1061 required this.state, 1062 required this.fingerprint, 1063 required this.createdAt, 1064 this.lastSeen, 1065 required this.queuedCount, 1066 required this.unreadCount, 1067 required this.disappearingDuration, 1068 required this.isMuted, 1069 }); 1070 1071 @override 1072 int get hashCode => 1073 contactId.hashCode ^ 1074 nickname.hashCode ^ 1075 state.hashCode ^ 1076 fingerprint.hashCode ^ 1077 createdAt.hashCode ^ 1078 lastSeen.hashCode ^ 1079 queuedCount.hashCode ^ 1080 unreadCount.hashCode ^ 1081 disappearingDuration.hashCode ^ 1082 isMuted.hashCode; 1083 1084 @override 1085 bool operator ==(Object other) => 1086 identical(this, other) || 1087 other is ContactInfo && 1088 runtimeType == other.runtimeType && 1089 contactId == other.contactId && 1090 nickname == other.nickname && 1091 state == other.state && 1092 fingerprint == other.fingerprint && 1093 createdAt == other.createdAt && 1094 lastSeen == other.lastSeen && 1095 queuedCount == other.queuedCount && 1096 unreadCount == other.unreadCount && 1097 disappearingDuration == other.disappearingDuration && 1098 isMuted == other.isMuted; 1099 } 1100 1101 /// Result of fetching messages from DHT. 1102 class DhtFetchResult { 1103 /// Newly received messages. 1104 final List<MessageInfo> messages; 1105 1106 /// Contact IDs that had reaction updates (need UI refresh). 1107 final List<Uint8List> contactsWithReactions; 1108 1109 /// Whether any GroupManagement messages were processed (need groups UI refresh). 1110 final bool hadGroupManagement; 1111 1112 const DhtFetchResult({ 1113 required this.messages, 1114 required this.contactsWithReactions, 1115 required this.hadGroupManagement, 1116 }); 1117 1118 @override 1119 int get hashCode => 1120 messages.hashCode ^ 1121 contactsWithReactions.hashCode ^ 1122 hadGroupManagement.hashCode; 1123 1124 @override 1125 bool operator ==(Object other) => 1126 identical(this, other) || 1127 other is DhtFetchResult && 1128 runtimeType == other.runtimeType && 1129 messages == other.messages && 1130 contactsWithReactions == other.contactsWithReactions && 1131 hadGroupManagement == other.hadGroupManagement; 1132 } 1133 1134 /// Result of publishing messages to the DHT. 1135 class DhtPublishResult { 1136 /// Number of messages published. 1137 final int publishedCount; 1138 1139 /// IDs of messages that were published. 1140 final List<Uint8List> publishedIds; 1141 1142 const DhtPublishResult({ 1143 required this.publishedCount, 1144 required this.publishedIds, 1145 }); 1146 1147 @override 1148 int get hashCode => publishedCount.hashCode ^ publishedIds.hashCode; 1149 1150 @override 1151 bool operator ==(Object other) => 1152 identical(this, other) || 1153 other is DhtPublishResult && 1154 runtimeType == other.runtimeType && 1155 publishedCount == other.publishedCount && 1156 publishedIds == other.publishedIds; 1157 } 1158 1159 /// State of the DHT transport. 1160 class DhtStateDto { 1161 /// Current state: "disconnected", "bootstrapping", "connected", "failed". 1162 final String state; 1163 1164 /// Number of connected peers (if connected). 1165 final int peerCount; 1166 1167 /// Error message (if failed). 1168 final String? error; 1169 1170 const DhtStateDto({required this.state, required this.peerCount, this.error}); 1171 1172 /// Create from internal DhtState. 1173 static Future<DhtStateDto> fromInternal({required DhtState state}) => 1174 RustLib.instance.api.crateApiDhtStateDtoFromInternal(state: state); 1175 1176 @override 1177 int get hashCode => state.hashCode ^ peerCount.hashCode ^ error.hashCode; 1178 1179 @override 1180 bool operator ==(Object other) => 1181 identical(this, other) || 1182 other is DhtStateDto && 1183 runtimeType == other.runtimeType && 1184 state == other.state && 1185 peerCount == other.peerCount && 1186 error == other.error; 1187 } 1188 1189 /// Information about a reassembled document. 1190 class DocumentInfo { 1191 /// Unique document ID. 1192 final Uint8List documentId; 1193 1194 /// Original filename. 1195 final String filename; 1196 1197 /// Total file size in bytes. 1198 final BigInt size; 1199 1200 /// Complete file content. 1201 final Uint8List content; 1202 1203 const DocumentInfo({ 1204 required this.documentId, 1205 required this.filename, 1206 required this.size, 1207 required this.content, 1208 }); 1209 1210 @override 1211 int get hashCode => 1212 documentId.hashCode ^ 1213 filename.hashCode ^ 1214 size.hashCode ^ 1215 content.hashCode; 1216 1217 @override 1218 bool operator ==(Object other) => 1219 identical(this, other) || 1220 other is DocumentInfo && 1221 runtimeType == other.runtimeType && 1222 documentId == other.documentId && 1223 filename == other.filename && 1224 size == other.size && 1225 content == other.content; 1226 } 1227 1228 /// DTO for a queued encrypted message ready for P2P delivery. 1229 class EncryptedPayload { 1230 /// Message ID (16 bytes). 1231 final Uint8List messageId; 1232 1233 /// Serialized encrypted message bytes (ready to send over WebSocket). 1234 final Uint8List payload; 1235 1236 const EncryptedPayload({required this.messageId, required this.payload}); 1237 1238 @override 1239 int get hashCode => messageId.hashCode ^ payload.hashCode; 1240 1241 @override 1242 bool operator ==(Object other) => 1243 identical(this, other) || 1244 other is EncryptedPayload && 1245 runtimeType == other.runtimeType && 1246 messageId == other.messageId && 1247 payload == other.payload; 1248 } 1249 1250 /// Final result of a completed exchange. 1251 class ExchangeComplete { 1252 /// The contact ID this exchange was with. 1253 final Uint8List contactId; 1254 1255 /// IDs of messages successfully delivered to the contact. 1256 final List<Uint8List> deliveredIds; 1257 1258 /// Messages received from the contact. 1259 final List<MessageInfo> receivedMessages; 1260 1261 const ExchangeComplete({ 1262 required this.contactId, 1263 required this.deliveredIds, 1264 required this.receivedMessages, 1265 }); 1266 1267 @override 1268 int get hashCode => 1269 contactId.hashCode ^ deliveredIds.hashCode ^ receivedMessages.hashCode; 1270 1271 @override 1272 bool operator ==(Object other) => 1273 identical(this, other) || 1274 other is ExchangeComplete && 1275 runtimeType == other.runtimeType && 1276 contactId == other.contactId && 1277 deliveredIds == other.deliveredIds && 1278 receivedMessages == other.receivedMessages; 1279 } 1280 1281 /// Opaque handle to an exchange session. 1282 class ExchangeHandle { 1283 /// Internal handle ID. 1284 final BigInt id; 1285 1286 const ExchangeHandle({required this.id}); 1287 1288 @override 1289 int get hashCode => id.hashCode; 1290 1291 @override 1292 bool operator ==(Object other) => 1293 identical(this, other) || 1294 other is ExchangeHandle && 1295 runtimeType == other.runtimeType && 1296 id == other.id; 1297 } 1298 1299 /// Result of processing exchange data. 1300 class ExchangeResultDto { 1301 /// True if exchange is complete. 1302 final bool complete; 1303 1304 /// Response data to send (if any). 1305 final Uint8List? response; 1306 1307 const ExchangeResultDto({required this.complete, this.response}); 1308 1309 @override 1310 int get hashCode => complete.hashCode ^ response.hashCode; 1311 1312 @override 1313 bool operator ==(Object other) => 1314 identical(this, other) || 1315 other is ExchangeResultDto && 1316 runtimeType == other.runtimeType && 1317 complete == other.complete && 1318 response == other.response; 1319 } 1320 1321 /// DTO for forwarding statistics. 1322 class ForwardingInfo { 1323 /// Total number of messages stored. 1324 final int messageCount; 1325 1326 /// Total storage used in bytes. 1327 final BigInt storageUsed; 1328 1329 /// Storage limit in bytes. 1330 final BigInt storageLimit; 1331 1332 /// Number of unique recipients we have messages for. 1333 final int uniqueRecipients; 1334 1335 /// Fill percentage (0-100). 1336 final double fillPercentage; 1337 1338 const ForwardingInfo({ 1339 required this.messageCount, 1340 required this.storageUsed, 1341 required this.storageLimit, 1342 required this.uniqueRecipients, 1343 required this.fillPercentage, 1344 }); 1345 1346 @override 1347 int get hashCode => 1348 messageCount.hashCode ^ 1349 storageUsed.hashCode ^ 1350 storageLimit.hashCode ^ 1351 uniqueRecipients.hashCode ^ 1352 fillPercentage.hashCode; 1353 1354 @override 1355 bool operator ==(Object other) => 1356 identical(this, other) || 1357 other is ForwardingInfo && 1358 runtimeType == other.runtimeType && 1359 messageCount == other.messageCount && 1360 storageUsed == other.storageUsed && 1361 storageLimit == other.storageLimit && 1362 uniqueRecipients == other.uniqueRecipients && 1363 fillPercentage == other.fillPercentage; 1364 } 1365 1366 /// Final result of a completed gossip exchange. 1367 class GossipExchangeComplete { 1368 /// Number of forwarded messages stored for future relay. 1369 final int storedCount; 1370 1371 /// Number of messages that were addressed to us. 1372 final int messagesForUsCount; 1373 1374 const GossipExchangeComplete({ 1375 required this.storedCount, 1376 required this.messagesForUsCount, 1377 }); 1378 1379 @override 1380 int get hashCode => storedCount.hashCode ^ messagesForUsCount.hashCode; 1381 1382 @override 1383 bool operator ==(Object other) => 1384 identical(this, other) || 1385 other is GossipExchangeComplete && 1386 runtimeType == other.runtimeType && 1387 storedCount == other.storedCount && 1388 messagesForUsCount == other.messagesForUsCount; 1389 } 1390 1391 /// Opaque handle to a gossip exchange session. 1392 class GossipExchangeHandle { 1393 /// Internal handle ID. 1394 final BigInt id; 1395 1396 const GossipExchangeHandle({required this.id}); 1397 1398 @override 1399 int get hashCode => id.hashCode; 1400 1401 @override 1402 bool operator ==(Object other) => 1403 identical(this, other) || 1404 other is GossipExchangeHandle && 1405 runtimeType == other.runtimeType && 1406 id == other.id; 1407 } 1408 1409 /// Result of finalizing a gossip exchange while keeping the transport alive. 1410 class GossipFinalizeKeepTransportResult { 1411 /// The gossip exchange result (stored count, messages for us). 1412 final GossipExchangeComplete result; 1413 1414 /// Handle to the preserved transport for creating new gossip rounds. 1415 final BigInt transportHandle; 1416 1417 const GossipFinalizeKeepTransportResult({ 1418 required this.result, 1419 required this.transportHandle, 1420 }); 1421 1422 @override 1423 int get hashCode => result.hashCode ^ transportHandle.hashCode; 1424 1425 @override 1426 bool operator ==(Object other) => 1427 identical(this, other) || 1428 other is GossipFinalizeKeepTransportResult && 1429 runtimeType == other.runtimeType && 1430 result == other.result && 1431 transportHandle == other.transportHandle; 1432 } 1433 1434 /// Result of processing a gossip response. 1435 class GossipResult { 1436 /// Number of messages stored for future forwarding. 1437 final int storedCount; 1438 1439 /// Number of messages that were for us (to be processed). 1440 final int messagesForUs; 1441 1442 const GossipResult({required this.storedCount, required this.messagesForUs}); 1443 1444 @override 1445 int get hashCode => storedCount.hashCode ^ messagesForUs.hashCode; 1446 1447 @override 1448 bool operator ==(Object other) => 1449 identical(this, other) || 1450 other is GossipResult && 1451 runtimeType == other.runtimeType && 1452 storedCount == other.storedCount && 1453 messagesForUs == other.messagesForUs; 1454 } 1455 1456 /// Group information. 1457 class GroupInfo { 1458 /// Unique group identifier. 1459 final Uint8List groupId; 1460 1461 /// Group name. 1462 final String name; 1463 1464 /// Creation timestamp (Unix seconds). 1465 final PlatformInt64 createdAt; 1466 1467 /// Number of members. 1468 final int memberCount; 1469 1470 /// Number of unread messages. 1471 final int unreadCount; 1472 1473 /// Disappearing message duration in seconds (0 = off). 1474 final BigInt disappearingDuration; 1475 1476 /// Whether notifications are muted. 1477 final bool isMuted; 1478 1479 /// Most recent message timestamp (for sorting). 1480 final PlatformInt64? lastMessageAt; 1481 1482 /// Whether the user has accepted the group invite. 1483 final bool accepted; 1484 1485 /// Whether the current user is the group creator (can remove members). 1486 final bool isCreator; 1487 1488 const GroupInfo({ 1489 required this.groupId, 1490 required this.name, 1491 required this.createdAt, 1492 required this.memberCount, 1493 required this.unreadCount, 1494 required this.disappearingDuration, 1495 required this.isMuted, 1496 this.lastMessageAt, 1497 required this.accepted, 1498 required this.isCreator, 1499 }); 1500 1501 @override 1502 int get hashCode => 1503 groupId.hashCode ^ 1504 name.hashCode ^ 1505 createdAt.hashCode ^ 1506 memberCount.hashCode ^ 1507 unreadCount.hashCode ^ 1508 disappearingDuration.hashCode ^ 1509 isMuted.hashCode ^ 1510 lastMessageAt.hashCode ^ 1511 accepted.hashCode ^ 1512 isCreator.hashCode; 1513 1514 @override 1515 bool operator ==(Object other) => 1516 identical(this, other) || 1517 other is GroupInfo && 1518 runtimeType == other.runtimeType && 1519 groupId == other.groupId && 1520 name == other.name && 1521 createdAt == other.createdAt && 1522 memberCount == other.memberCount && 1523 unreadCount == other.unreadCount && 1524 disappearingDuration == other.disappearingDuration && 1525 isMuted == other.isMuted && 1526 lastMessageAt == other.lastMessageAt && 1527 accepted == other.accepted && 1528 isCreator == other.isCreator; 1529 } 1530 1531 /// Group member information. 1532 class GroupMemberInfoDto { 1533 /// Contact ID. 1534 final Uint8List contactId; 1535 1536 /// Nickname. 1537 final String? nickname; 1538 1539 /// Role (0=member, 1=admin). 1540 final int role; 1541 1542 const GroupMemberInfoDto({ 1543 required this.contactId, 1544 this.nickname, 1545 required this.role, 1546 }); 1547 1548 @override 1549 int get hashCode => contactId.hashCode ^ nickname.hashCode ^ role.hashCode; 1550 1551 @override 1552 bool operator ==(Object other) => 1553 identical(this, other) || 1554 other is GroupMemberInfoDto && 1555 runtimeType == other.runtimeType && 1556 contactId == other.contactId && 1557 nickname == other.nickname && 1558 role == other.role; 1559 } 1560 1561 /// Opaque handle to a handshake session. 1562 class HandshakeHandle { 1563 /// Internal handle ID. 1564 final BigInt id; 1565 1566 const HandshakeHandle({required this.id}); 1567 1568 @override 1569 int get hashCode => id.hashCode; 1570 1571 @override 1572 bool operator ==(Object other) => 1573 identical(this, other) || 1574 other is HandshakeHandle && 1575 runtimeType == other.runtimeType && 1576 id == other.id; 1577 } 1578 1579 /// Result of processing a handshake message. 1580 class HandshakeResult { 1581 /// True if handshake is complete. 1582 final bool complete; 1583 1584 /// Response message to send (if any). 1585 final Uint8List? response; 1586 1587 /// Remote party's identity public key (available when complete). 1588 final Uint8List? remoteIdentity; 1589 1590 const HandshakeResult({ 1591 required this.complete, 1592 this.response, 1593 this.remoteIdentity, 1594 }); 1595 1596 @override 1597 int get hashCode => 1598 complete.hashCode ^ response.hashCode ^ remoteIdentity.hashCode; 1599 1600 @override 1601 bool operator ==(Object other) => 1602 identical(this, other) || 1603 other is HandshakeResult && 1604 runtimeType == other.runtimeType && 1605 complete == other.complete && 1606 response == other.response && 1607 remoteIdentity == other.remoteIdentity; 1608 } 1609 1610 /// Identity information. 1611 class IdentityInfo { 1612 /// Ed25519 identity public key. 1613 final Uint8List identityPublic; 1614 1615 /// X25519 exchange public key. 1616 final Uint8List exchangePublic; 1617 1618 /// Human-readable fingerprint. 1619 final String fingerprint; 1620 1621 /// Creation timestamp (Unix seconds). 1622 final PlatformInt64 createdAt; 1623 1624 const IdentityInfo({ 1625 required this.identityPublic, 1626 required this.exchangePublic, 1627 required this.fingerprint, 1628 required this.createdAt, 1629 }); 1630 1631 @override 1632 int get hashCode => 1633 identityPublic.hashCode ^ 1634 exchangePublic.hashCode ^ 1635 fingerprint.hashCode ^ 1636 createdAt.hashCode; 1637 1638 @override 1639 bool operator ==(Object other) => 1640 identical(this, other) || 1641 other is IdentityInfo && 1642 runtimeType == other.runtimeType && 1643 identityPublic == other.identityPublic && 1644 exchangePublic == other.exchangePublic && 1645 fingerprint == other.fingerprint && 1646 createdAt == other.createdAt; 1647 } 1648 1649 /// DTO for an incoming iroh message. 1650 class IrohIncomingMessage { 1651 /// The sender's iroh EndpointId bytes (32 bytes). 1652 final Uint8List senderNodeId; 1653 1654 /// The encrypted message payload. 1655 final Uint8List payload; 1656 1657 const IrohIncomingMessage({ 1658 required this.senderNodeId, 1659 required this.payload, 1660 }); 1661 1662 @override 1663 int get hashCode => senderNodeId.hashCode ^ payload.hashCode; 1664 1665 @override 1666 bool operator ==(Object other) => 1667 identical(this, other) || 1668 other is IrohIncomingMessage && 1669 runtimeType == other.runtimeType && 1670 senderNodeId == other.senderNodeId && 1671 payload == other.payload; 1672 } 1673 1674 /// DTO for iroh transport state. 1675 class IrohStateDto { 1676 /// State string: "disconnected", "starting", "running", "failed" 1677 final String state; 1678 1679 /// Our iroh EndpointId (hex string), if running. 1680 final String? endpointId; 1681 1682 /// Error message, if failed. 1683 final String? error; 1684 1685 const IrohStateDto({required this.state, this.endpointId, this.error}); 1686 1687 @override 1688 int get hashCode => state.hashCode ^ endpointId.hashCode ^ error.hashCode; 1689 1690 @override 1691 bool operator ==(Object other) => 1692 identical(this, other) || 1693 other is IrohStateDto && 1694 runtimeType == other.runtimeType && 1695 state == other.state && 1696 endpointId == other.endpointId && 1697 error == other.error; 1698 } 1699 1700 /// Message information. 1701 class MessageInfo { 1702 /// Unique message identifier. 1703 final Uint8List messageId; 1704 1705 /// Contact ID (sender or recipient). 1706 final Uint8List contactId; 1707 1708 /// Content type (1=Text, 2=Document). 1709 final int contentType; 1710 1711 /// Message content. 1712 final Uint8List content; 1713 1714 /// Filename (for documents). 1715 final String? filename; 1716 1717 /// True if outbound, false if inbound. 1718 final bool isOutbound; 1719 1720 /// Message state. 1721 final int state; 1722 1723 /// Creation/received timestamp (Unix seconds). 1724 final PlatformInt64 timestamp; 1725 1726 /// Expiration timestamp (Unix seconds). 1727 final PlatformInt64? expiresAt; 1728 1729 /// Transport used for delivery ("ble", "dht", "relay"). 1730 final String? deliveryTransport; 1731 1732 /// Delivery timestamp (Unix seconds). 1733 final PlatformInt64? deliveredAt; 1734 1735 /// Last error message (if delivery failed). 1736 final String? lastError; 1737 1738 /// Reaction summaries (emoji -> count). 1739 final List<ReactionSummaryDto> reactions; 1740 1741 /// Disappearing message duration in seconds (0 = standard). 1742 final BigInt disappearingDuration; 1743 1744 /// Sender contact ID (for group inbound messages). 1745 final Uint8List? senderId; 1746 1747 /// Group ID (if group message). 1748 final Uint8List? groupId; 1749 1750 const MessageInfo({ 1751 required this.messageId, 1752 required this.contactId, 1753 required this.contentType, 1754 required this.content, 1755 this.filename, 1756 required this.isOutbound, 1757 required this.state, 1758 required this.timestamp, 1759 this.expiresAt, 1760 this.deliveryTransport, 1761 this.deliveredAt, 1762 this.lastError, 1763 required this.reactions, 1764 required this.disappearingDuration, 1765 this.senderId, 1766 this.groupId, 1767 }); 1768 1769 @override 1770 int get hashCode => 1771 messageId.hashCode ^ 1772 contactId.hashCode ^ 1773 contentType.hashCode ^ 1774 content.hashCode ^ 1775 filename.hashCode ^ 1776 isOutbound.hashCode ^ 1777 state.hashCode ^ 1778 timestamp.hashCode ^ 1779 expiresAt.hashCode ^ 1780 deliveryTransport.hashCode ^ 1781 deliveredAt.hashCode ^ 1782 lastError.hashCode ^ 1783 reactions.hashCode ^ 1784 disappearingDuration.hashCode ^ 1785 senderId.hashCode ^ 1786 groupId.hashCode; 1787 1788 @override 1789 bool operator ==(Object other) => 1790 identical(this, other) || 1791 other is MessageInfo && 1792 runtimeType == other.runtimeType && 1793 messageId == other.messageId && 1794 contactId == other.contactId && 1795 contentType == other.contentType && 1796 content == other.content && 1797 filename == other.filename && 1798 isOutbound == other.isOutbound && 1799 state == other.state && 1800 timestamp == other.timestamp && 1801 expiresAt == other.expiresAt && 1802 deliveryTransport == other.deliveryTransport && 1803 deliveredAt == other.deliveredAt && 1804 lastError == other.lastError && 1805 reactions == other.reactions && 1806 disappearingDuration == other.disappearingDuration && 1807 senderId == other.senderId && 1808 groupId == other.groupId; 1809 } 1810 1811 /// Network transport preference for message delivery. 1812 /// 1813 /// Controls the priority order when multiple transports are available. 1814 enum NetworkTransportPreference { 1815 /// Prefer DHT for decentralized delivery (default). 1816 dhtPreferred, 1817 1818 /// Prefer relay server for faster delivery. 1819 relayPreferred, 1820 1821 /// Use DHT only, never use relays. 1822 dhtOnly, 1823 1824 /// Use relay only, never use DHT. 1825 relayOnly; 1826 1827 static Future<NetworkTransportPreference> default_() => 1828 RustLib.instance.api.crateApiNetworkTransportPreferenceDefault(); 1829 } 1830 1831 /// Pending contact from QR scan (not yet confirmed). 1832 class PendingContact { 1833 /// Generated contact ID. 1834 final Uint8List contactId; 1835 1836 /// Ed25519 identity public key. 1837 final Uint8List identityPublic; 1838 1839 /// X25519 exchange public key. 1840 final Uint8List exchangePublic; 1841 1842 /// Human-readable fingerprint. 1843 final String fingerprint; 1844 1845 const PendingContact({ 1846 required this.contactId, 1847 required this.identityPublic, 1848 required this.exchangePublic, 1849 required this.fingerprint, 1850 }); 1851 1852 @override 1853 int get hashCode => 1854 contactId.hashCode ^ 1855 identityPublic.hashCode ^ 1856 exchangePublic.hashCode ^ 1857 fingerprint.hashCode; 1858 1859 @override 1860 bool operator ==(Object other) => 1861 identical(this, other) || 1862 other is PendingContact && 1863 runtimeType == other.runtimeType && 1864 contactId == other.contactId && 1865 identityPublic == other.identityPublic && 1866 exchangePublic == other.exchangePublic && 1867 fingerprint == other.fingerprint; 1868 } 1869 1870 /// Summary of reactions for a message. 1871 class ReactionSummaryDto { 1872 /// The emoji. 1873 final String emoji; 1874 1875 /// Number of this reaction. 1876 final int count; 1877 1878 /// Whether the current user has added this reaction. 1879 final bool includesSelf; 1880 1881 const ReactionSummaryDto({ 1882 required this.emoji, 1883 required this.count, 1884 required this.includesSelf, 1885 }); 1886 1887 @override 1888 int get hashCode => emoji.hashCode ^ count.hashCode ^ includesSelf.hashCode; 1889 1890 @override 1891 bool operator ==(Object other) => 1892 identical(this, other) || 1893 other is ReactionSummaryDto && 1894 runtimeType == other.runtimeType && 1895 emoji == other.emoji && 1896 count == other.count && 1897 includesSelf == other.includesSelf; 1898 } 1899 1900 /// State of the relay transport. 1901 class RelayStateDto { 1902 /// Current state: "disconnected", "connecting", "connected", "failed". 1903 final String state; 1904 1905 /// URL of the connected relay (if connected). 1906 final String? connectedRelay; 1907 1908 /// Error message (if failed). 1909 final String? error; 1910 1911 const RelayStateDto({required this.state, this.connectedRelay, this.error}); 1912 1913 /// Create from internal RelayState. 1914 static Future<RelayStateDto> fromInternal({required RelayState state}) => 1915 RustLib.instance.api.crateApiRelayStateDtoFromInternal(state: state); 1916 1917 @override 1918 int get hashCode => state.hashCode ^ connectedRelay.hashCode ^ error.hashCode; 1919 1920 @override 1921 bool operator ==(Object other) => 1922 identical(this, other) || 1923 other is RelayStateDto && 1924 runtimeType == other.runtimeType && 1925 state == other.state && 1926 connectedRelay == other.connectedRelay && 1927 error == other.error; 1928 } 1929 1930 /// DTO for reputation information. 1931 class ReputationInfo { 1932 /// Total messages forwarded. 1933 final BigInt messagesForwarded; 1934 1935 /// Total messages successfully delivered (confirmed). 1936 final BigInt messagesDelivered; 1937 1938 /// Unique recipients helped. 1939 final int recipientsHelped; 1940 1941 /// Total XP earned. 1942 final BigInt totalXp; 1943 1944 /// Current level (1-5). 1945 final int currentLevel; 1946 1947 /// Level title (e.g., "Courier"). 1948 final String levelTitle; 1949 1950 /// Progress to next level (0.0 to 1.0). 1951 final double levelProgress; 1952 1953 /// XP needed for next level. 1954 final BigInt xpForNextLevel; 1955 1956 /// Current streak in days. 1957 final int currentStreak; 1958 1959 const ReputationInfo({ 1960 required this.messagesForwarded, 1961 required this.messagesDelivered, 1962 required this.recipientsHelped, 1963 required this.totalXp, 1964 required this.currentLevel, 1965 required this.levelTitle, 1966 required this.levelProgress, 1967 required this.xpForNextLevel, 1968 required this.currentStreak, 1969 }); 1970 1971 @override 1972 int get hashCode => 1973 messagesForwarded.hashCode ^ 1974 messagesDelivered.hashCode ^ 1975 recipientsHelped.hashCode ^ 1976 totalXp.hashCode ^ 1977 currentLevel.hashCode ^ 1978 levelTitle.hashCode ^ 1979 levelProgress.hashCode ^ 1980 xpForNextLevel.hashCode ^ 1981 currentStreak.hashCode; 1982 1983 @override 1984 bool operator ==(Object other) => 1985 identical(this, other) || 1986 other is ReputationInfo && 1987 runtimeType == other.runtimeType && 1988 messagesForwarded == other.messagesForwarded && 1989 messagesDelivered == other.messagesDelivered && 1990 recipientsHelped == other.recipientsHelped && 1991 totalXp == other.totalXp && 1992 currentLevel == other.currentLevel && 1993 levelTitle == other.levelTitle && 1994 levelProgress == other.levelProgress && 1995 xpForNextLevel == other.xpForNextLevel && 1996 currentStreak == other.currentStreak; 1997 } 1998 1999 /// Search result from message search. 2000 class SearchResultInfo { 2001 /// Message ID. 2002 final Uint8List messageId; 2003 2004 /// Contact ID. 2005 final Uint8List contactId; 2006 2007 /// Whether the message was sent by us. 2008 final bool isOutbound; 2009 2010 /// Content type byte. 2011 final int contentType; 2012 2013 /// Message content (text or filename for documents). 2014 final Uint8List content; 2015 2016 /// Filename for document messages. 2017 final String? filename; 2018 2019 /// Unix timestamp. 2020 final PlatformInt64 timestamp; 2021 2022 const SearchResultInfo({ 2023 required this.messageId, 2024 required this.contactId, 2025 required this.isOutbound, 2026 required this.contentType, 2027 required this.content, 2028 this.filename, 2029 required this.timestamp, 2030 }); 2031 2032 @override 2033 int get hashCode => 2034 messageId.hashCode ^ 2035 contactId.hashCode ^ 2036 isOutbound.hashCode ^ 2037 contentType.hashCode ^ 2038 content.hashCode ^ 2039 filename.hashCode ^ 2040 timestamp.hashCode; 2041 2042 @override 2043 bool operator ==(Object other) => 2044 identical(this, other) || 2045 other is SearchResultInfo && 2046 runtimeType == other.runtimeType && 2047 messageId == other.messageId && 2048 contactId == other.contactId && 2049 isOutbound == other.isOutbound && 2050 contentType == other.contentType && 2051 content == other.content && 2052 filename == other.filename && 2053 timestamp == other.timestamp; 2054 } 2055 2056 /// Transport address for FFI. 2057 class TransportAddressDto { 2058 /// The transport type. 2059 final TransportTypeDto transportType; 2060 2061 /// The raw address bytes. 2062 final Uint8List address; 2063 2064 const TransportAddressDto({ 2065 required this.transportType, 2066 required this.address, 2067 }); 2068 2069 /// Create from internal TransportAddress. 2070 static Future<TransportAddressDto?> fromInternal({ 2071 required TransportAddress addr, 2072 }) => 2073 RustLib.instance.api.crateApiTransportAddressDtoFromInternal(addr: addr); 2074 2075 @override 2076 int get hashCode => transportType.hashCode ^ address.hashCode; 2077 2078 @override 2079 bool operator ==(Object other) => 2080 identical(this, other) || 2081 other is TransportAddressDto && 2082 runtimeType == other.runtimeType && 2083 transportType == other.transportType && 2084 address == other.address; 2085 } 2086 2087 /// Information about an available transport. 2088 class TransportInfoDto { 2089 /// The transport type. 2090 final TransportTypeDto transportType; 2091 2092 /// Human-readable name. 2093 final String name; 2094 2095 /// Whether the transport is currently available. 2096 final bool available; 2097 2098 /// Transport priority (lower = higher priority). 2099 final int priority; 2100 2101 const TransportInfoDto({ 2102 required this.transportType, 2103 required this.name, 2104 required this.available, 2105 required this.priority, 2106 }); 2107 2108 @override 2109 int get hashCode => 2110 transportType.hashCode ^ 2111 name.hashCode ^ 2112 available.hashCode ^ 2113 priority.hashCode; 2114 2115 @override 2116 bool operator ==(Object other) => 2117 identical(this, other) || 2118 other is TransportInfoDto && 2119 runtimeType == other.runtimeType && 2120 transportType == other.transportType && 2121 name == other.name && 2122 available == other.available && 2123 priority == other.priority; 2124 } 2125 2126 /// Transport delivery statistics. 2127 class TransportStatsInfo { 2128 /// Messages delivered via BLE. 2129 final int bleCount; 2130 2131 /// Messages delivered via DHT. 2132 final int dhtCount; 2133 2134 /// Messages delivered via Relay. 2135 final int relayCount; 2136 2137 /// Messages delivered via Iroh. 2138 final int irohCount; 2139 2140 const TransportStatsInfo({ 2141 required this.bleCount, 2142 required this.dhtCount, 2143 required this.relayCount, 2144 required this.irohCount, 2145 }); 2146 2147 static Future<TransportStatsInfo> default_() => 2148 RustLib.instance.api.crateApiTransportStatsInfoDefault(); 2149 2150 @override 2151 int get hashCode => 2152 bleCount.hashCode ^ 2153 dhtCount.hashCode ^ 2154 relayCount.hashCode ^ 2155 irohCount.hashCode; 2156 2157 @override 2158 bool operator ==(Object other) => 2159 identical(this, other) || 2160 other is TransportStatsInfo && 2161 runtimeType == other.runtimeType && 2162 bleCount == other.bleCount && 2163 dhtCount == other.dhtCount && 2164 relayCount == other.relayCount && 2165 irohCount == other.irohCount; 2166 } 2167 2168 /// Transport type for FFI. 2169 /// 2170 /// Maps to the internal TransportType enum. 2171 enum TransportTypeDto { 2172 /// Bluetooth Low Energy - proximity-based messaging. 2173 ble, 2174 2175 /// Tor onion services - anonymous global messaging. 2176 tor, 2177 2178 /// Distributed Hash Table - decentralized message storage. 2179 dht, 2180 2181 /// Invisible Internet Project - anonymous overlay network. 2182 i2P, 2183 2184 /// Relay server - push-based message delivery. 2185 relay, 2186 2187 /// Iroh - QUIC-based P2P with NAT traversal. 2188 iroh; 2189 2190 /// Convert from the internal TransportType. 2191 static Future<TransportTypeDto?> fromInternal({required TransportType tt}) => 2192 RustLib.instance.api.crateApiTransportTypeDtoFromInternal(tt: tt); 2193 2194 /// Convert to the internal TransportType. 2195 Future<TransportType> toInternal() => 2196 RustLib.instance.api.crateApiTransportTypeDtoToInternal(that: this); 2197 }