connectWithBunkerUrl method

Future<BunkerConnection?> connectWithBunkerUrl(
  1. String bunkerUrl, {
  2. dynamic authCallback(
    1. String
    )?,
})

Connects to a bunker using a bunker URL (bunker://) authCallback is called with the auth URL if the bunker requires authentication

Implementation

Future<BunkerConnection?> connectWithBunkerUrl(
  String bunkerUrl, {
  Function(String)? authCallback,
}) async {
  final uri = Uri.parse(bunkerUrl);
  if (uri.scheme != 'bunker') {
    throw ArgumentError('Invalid bunker URL scheme');
  }

  final remotePubkey = uri.host;
  final relays = uri.queryParametersAll['relay'] ?? [];
  final secret = uri.queryParameters['secret'];

  if (relays.isEmpty) {
    throw ArgumentError('At least one relay is required in bunker URL');
  }

  if (secret == null) {
    throw ArgumentError('Secret parameter is required in bunker URL');
  }

  final keyPair = Bip340.generatePrivateKey();
  final localEventSigner = Bip340EventSigner(
    privateKey: keyPair.privateKey,
    publicKey: keyPair.publicKey,
  );

  final request = BunkerRequest(
    method: BunkerRequestMethods.connect,
    params: [remotePubkey, secret],
  );

  final encryptedRequest = await localEventSigner.encryptNip44(
    plaintext: jsonEncode(request),
    recipientPubKey: remotePubkey,
  );

  final requestEvent = Nip01Event(
    pubKey: localEventSigner.publicKey,
    kind: BunkerRequest.kKind,
    tags: [
      ["p", remotePubkey],
    ],
    content: encryptedRequest!,
  );

  await localEventSigner.sign(requestEvent);
  final broadcastRes =
      _broadcast.broadcast(nostrEvent: requestEvent, specificRelays: relays);
  await broadcastRes.broadcastDoneFuture;

  final subscription = _requests.subscription(
    explicitRelays: relays,
    filters: [
      Filter(
        authors: [remotePubkey],
        kinds: [BunkerRequest.kKind],
        pTags: [localEventSigner.publicKey],
        since: someTimeAgo(),
      ),
    ],
  );
  BunkerConnection? result;

  await for (final event in subscription.stream
      .timeout(Duration(seconds: kMaxWaitingTimeForConnectionSeconds))) {
    final decryptedContent = await localEventSigner.decryptNip44(
      ciphertext: event.content,
      senderPubKey: remotePubkey,
    );

    final response = jsonDecode(decryptedContent!);

    if (response["id"] != request.id) continue;

    if (response["result"] == "auth_url") {
      if (authCallback != null) {
        authCallback(response["error"]);
      }
      continue;
    }

    if (response["result"] == "ack") {
      result = BunkerConnection(
        privateKey: localEventSigner.privateKey!,
        remotePubkey: remotePubkey,
        relays: relays,
      );
      break;
    }
  }
  await _requests.closeSubscription(subscription.requestId);
  return result;
}