import { EventEmitter } from "node:events"; import type { KeyPair } from "../crypto/keypair.js"; import type { NetworkNode } from "../types/peer.js"; export type TcpRelayPoolOptions = { relays: NetworkNode[]; selfKeyPair: KeyPair; maxConnections?: number; /** Optional debug label inherited by underlying TcpRelayClients. */ label?: string; }; export type TcpRelayPoolEvents = { /** A friend is now online on at least one relay. */ friendOnline: (friendPublicKey: Uint8Array) => void; /** A friend is no longer online on any relay. */ friendOffline: (friendPublicKey: Uint8Array) => void; /** Inbound DATA forwarded by some relay. */ peerData: (friendPublicKey: Uint8Array, payload: Uint8Array) => void; /** Inbound OOB_RECV (used for friend requests via TCP). */ oob: (senderPublicKey: Uint8Array, payload: Uint8Array) => void; /** Inbound onion response routed back through a relay (announce / onion data). */ onionResponse: (packet: Uint8Array) => void; /** Number of currently-connected relays changed (for diagnostics). */ status: (connected: number, total: number) => void; }; export declare class TcpRelayPool extends EventEmitter { #private; constructor(opts: TcpRelayPoolOptions); /** * Initialize the pool. Selects up to `maxConnections` relays from the * supplied list (picks them in order; the operator can prioritize by * sorting). Returns the count of initial connections successfully * opened. */ start(): Promise; /** Tear down all relay connections. */ stop(): Promise; /** * Dynamically add a TCP relay to the pool — used when a friend's * DHT-PK extras advertise a relay address we weren't already on. * Without this, two peers on disjoint relay sets can never establish * a TCP path even if both relays are reachable. Idempotent: skips if * the host:port pair is already in the pool. * * Returns the index of the relay (existing or newly added). */ addRelay(node: NetworkNode): number; /** * Ask every connected relay to route packets for this friend. Idempotent; * subsequent calls for an already-requested key are no-ops on each * underlying client. */ requestRoute(friendPublicKey: Uint8Array): void; /** True if at least one relay reports this friend currently online. */ isFriendOnline(friendPublicKey: Uint8Array): boolean; /** * OOB_SEND — fire-and-forget delivery to any peer connected to any of * our relays, bypassing routing. Used for the initial cookie request * / cookie response / crypto handshake exchange before either side * has issued ROUTING_REQUEST. Mirrors toxcore's tcp_send_oob_packet * which net_crypto.c calls via send_oob_packet for the same purpose. * Returns the count of relays the OOB packet was written to. */ sendOobToFriend(friendPublicKey: Uint8Array, payload: Uint8Array): number; /** * Forward a DATA payload to a friend through any relay where they're * online. Returns the count of relays that accepted the write — 0 if * the friend isn't online on any TCP relay. */ sendToFriend(friendPublicKey: Uint8Array, payload: Uint8Array, droppable?: boolean): number; /** Diagnostics. */ connectedCount(): number; /** * Send an onion request through up to `fanout` connected relays. * * Each relay forwards the packet to node B over UDP and routes the onion * response back over TCP (surfaced via the pool's "onionResponse" event). * Returns how many relays accepted it (0 = none connected). * * **What fanout buys.** The SAME request is handed to several relays, so it * is redundancy, not extra reach: if one relay is unreachable, overloaded, or * simply cannot get to node B, another copy still arrives. The peer sees one * request either way — duplicates are dropped by the onion layer. * * **What it costs.** Linear in every resource this path spends: N relays * means N times the TCP traffic and N times the work at the relay end. It * does NOT multiply local crypto — the onion packet is built once and reused, * so the X25519 cost is paid once regardless of fanout. * * **Choosing a value.** `1` is cheapest and correct on a stable link where * the first relay is reliable; a lost request just waits for the next sweep. * `2` (the default) is the useful middle: it covers a single dead relay, * which is the common failure, at double the traffic on a path that is * already small. Above 3 the added redundancy is mostly wasted — you are * paying for the case where three independent relays fail at once, which in * practice means the network is down, not the relays. * * Override with `DECENT_ONION_RELAY_FANOUT`. */ sendOnionRequest(packet: Uint8Array, fanout?: number): number; /** * Snapshot of currently-connected relays for inclusion in DHT-PK * announce extras. Mirrors toxcore's `tcp_copy_connected_relays`. * Returns up to `max` entries, each with the relay's public key and * its host:port — enough for the receiver to add the relay to its * own pool via TcpRelayPool.addRelay and start routing through it. */ connectedRelays(max?: number): Array<{ host: string; port: number; serverPublicKey: Uint8Array; }>; on(event: E, listener: TcpRelayPoolEvents[E]): this; once(event: E, listener: TcpRelayPoolEvents[E]): this; off(event: E, listener: TcpRelayPoolEvents[E]): this; }