sendViaServer method

Future<SendResult> sendViaServer(
  1. PeerId serverId,
  2. PeerId recipient,
  3. Uint8List payload,
  4. MessagePriority priority,
  5. Duration? expiry, {
  6. String? folderPath,
  7. bool persistent = false,
  8. String? messageId,
  9. SFMessageFlags flags = SFMessageFlags.none,
})

Send message via specific S&F server

Implementation

Future<SendResult> sendViaServer(
  PeerId serverId,
  PeerId recipient,
  Uint8List payload,
  MessagePriority priority,
  Duration? expiry, {
  String? folderPath,
  bool persistent = false,
  String? messageId,
  SFMessageFlags flags = SFMessageFlags.none,
}) async {
  _logger.info('Sending via server: ${serverId.toString().substring(0, 12)}...');

  P2PStream? stream;
  try {
    // Open stream to S&F server using MSA (Mail Submission Agent) protocol
    final context = Context();
    stream = await host.newStream(
      serverId,
      [SubmissionHandler.protocolId],
      context,
    ).timeout(config.connectionTimeout);

    // Submit message to server via MSA
    final ack = await SubmissionHandler.submitMessage(
      stream,
      recipient,
      payload,
      priority: priority,
      expiry: expiry,
      folderPath: folderPath,
      persistent: persistent,
      messageId: messageId,
      flags: flags,
    ).timeout(config.messageTimeout);

    if (ack.success) {
      _logger.info('Message stored successfully: ${ack.messageId}');

      // Protect the S&F server connection so it won't be closed by cleanup
      host.connManager.protect(serverId, 'ricochet-sf-server');
      _logger.fine('Protected connection to S&F server ${serverId.toBase58().substring(0, 12)}...');

      return SendResult.success(
        deliveredDirectly: false,
        storedAtServer: serverId,
        messageId: ack.messageId,
        estimatedDeliveryTime: ack.estimatedDeliveryTime != null
            ? Duration(milliseconds: ack.estimatedDeliveryTime!)
            : null,
      );
    } else {
      return SendResult.failure(ack.errorMessage ?? 'Store failed');
    }

  } on TimeoutException {
    throw Exception('Operation timed out');
  } catch (e) {
    throw Exception('Failed to send via server: $e');
  } finally {
    // The stream is closed on every path: a stream left open holds a slot
    // on the connection for as long as the session lives, and a sender that
    // never closes one runs the connection out of slots.
    if (stream != null && !stream.isClosed) {
      try {
        await stream.close();
      } catch (e) {
        _logger.fine('Error closing stream: $e');
      }
    }
  }
}