connectRelay method

Future<Tuple<bool, String>> connectRelay({
  1. required String dirtyUrl,
  2. required ConnectionSource connectionSource,
  3. int connectTimeout = DEFAULT_WEB_SOCKET_CONNECT_TIMEOUT,
})

Connects to a relay to the relay pool. Returns a tuple with the first element being a boolean indicating success \ and the second element being a string with the error message if any.

Implementation

Future<Tuple<bool, String>> connectRelay({
  required String dirtyUrl,
  required ConnectionSource connectionSource,
  int connectTimeout = DEFAULT_WEB_SOCKET_CONNECT_TIMEOUT,
}) async {
  String? url = cleanRelayUrl(dirtyUrl);
  if (url == null) {
    updateRelayConnectivity();
    return Tuple(false, "unclean url");
  }
  if (globalState.blockedRelays.contains(url)) {
    updateRelayConnectivity();
    return Tuple(false, "relay is blocked");
  }

  if (isRelayConnected(url)) {
    Logger.log.t(() => "relay already connected: $url");
    updateRelayConnectivity();
    return Tuple(true, "");
  }

  if (isRelayConnecting(url)) {
    Logger.log.t(() => "relay is already connecting: $url");
    final inFlightConnect = _connectReadyCompleters[url];
    if (inFlightConnect != null) {
      final connected = await inFlightConnect.future;
      updateRelayConnectivity();
      return Tuple(
        connected,
        connected
            ? "relay finished connecting"
            : "relay failed while connecting",
      );
    }
    updateRelayConnectivity();
    return Tuple(false, "relay is still connecting");
  }
  RelayConnectivity? relayConnectivity = globalState.relays[url];
  final connectCompleter = Completer<bool>();
  _connectReadyCompleters[url] = connectCompleter;

  try {
    if (relayConnectivity == null) {
      relayConnectivity = RelayConnectivity<T>(
        relay: Relay(url: url, connectionSource: connectionSource),
        specificEngineData: engineAdditionalDataFactory?.call(),
      );
      globalState.relays[url] = relayConnectivity;
    }

    relayConnectivity.relay.tryingToConnect();

    /// TO BE REMOVED, ONCE WE FIND A WAY OF AVOIDING PROBLEM WHEN CONNECTING TO THIS
    if (url.startsWith("wss://brb.io")) {
      relayConnectivity.relay.failedToConnect();
      if (!connectCompleter.isCompleted) {
        connectCompleter.complete(false);
      }
      if (identical(_connectReadyCompleters[url], connectCompleter)) {
        _connectReadyCompleters.remove(url);
      }
      updateRelayConnectivity();
      return Tuple(false, "bad relay");
    }

    Logger.log.i(() => "connecting to relay $dirtyUrl");

    relayConnectivity.relayTransport = nostrTransportFactory(
      url,
      onReconnect: () {
        reSubscribeInFlightSubscriptions(relayConnectivity!);
        updateRelayConnectivity();
      },
      onDisconnect: (code, error, reason) {
        relayConnectivity!.stats.connectionErrors++;
        updateRelayConnectivity();
      },
    );
    // Start listening immediately so we don't miss early frames such as
    // relay AUTH challenges that may arrive before the transport reports
    // itself fully open.
    _startListeningToSocket(relayConnectivity);
    final opened = await _waitForTransportOpen(
      relayConnectivity.relayTransport!,
      timeoutSeconds: connectTimeout,
    );
    if (!opened) {
      throw TimeoutException(
        "Future not completed",
        Duration(seconds: connectTimeout),
      );
    }

    Logger.log.i(() => "connected to relay: $url");
    relayConnectivity.relay.succeededToConnect();
    relayConnectivity.stats.connections++;
    getRelayInfo(url).then((info) {
      relayConnectivity!.relayInfo = info;
    });
    if (!connectCompleter.isCompleted) {
      connectCompleter.complete(true);
    }
    if (identical(_connectReadyCompleters[url], connectCompleter)) {
      _connectReadyCompleters.remove(url);
    }
    updateRelayConnectivity();
    return Tuple(true, "");
  } catch (e) {
    Logger.log.e(() => "!! could not connect to $url -> $e");
    await relayConnectivity!.close();
  }
  relayConnectivity.relay.failedToConnect();
  relayConnectivity.stats.connectionErrors++;
  if (!connectCompleter.isCompleted) {
    connectCompleter.complete(false);
  }
  if (identical(_connectReadyCompleters[url], connectCompleter)) {
    _connectReadyCompleters.remove(url);
  }
  updateRelayConnectivity();
  return Tuple(false, "could not connect to $url");
}