import { randomBytes } from '@libp2p/crypto';
import { serviceCapabilities } from '@libp2p/interface';
import { AdaptiveTimeout } from '@libp2p/utils/adaptive-timeout';
import { byteStream } from 'it-byte-stream';
import { setMaxListeners } from 'main-event';
const DEFAULT_PING_INTERVAL_MS = 10000;
const PROTOCOL_VERSION = '1.0.0';
const PROTOCOL_NAME = 'ping';
const PROTOCOL_PREFIX = 'ipfs';
const PING_LENGTH = 32;
const DEFAULT_ABORT_CONNECTION_ON_PING_FAILURE = true;
export class ConnectionMonitor {
    protocol;
    components;
    log;
    heartbeatInterval;
    pingIntervalMs;
    abortController;
    timeout;
    abortConnectionOnPingFailure;
    constructor(components, init = {}) {
        this.components = components;
        this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`;
        this.log = components.logger.forComponent('libp2p:connection-monitor');
        this.pingIntervalMs = init.pingInterval ?? DEFAULT_PING_INTERVAL_MS;
        this.abortConnectionOnPingFailure = init.abortConnectionOnPingFailure ?? DEFAULT_ABORT_CONNECTION_ON_PING_FAILURE;
        this.timeout = new AdaptiveTimeout({
            ...(init.pingTimeout ?? {}),
            metrics: components.metrics,
            metricName: 'libp2p_connection_monitor_ping_time_milliseconds'
        });
    }
    [Symbol.toStringTag] = '@libp2p/connection-monitor';
    [serviceCapabilities] = [
        '@libp2p/connection-monitor'
    ];
    start() {
        this.abortController = new AbortController();
        setMaxListeners(Infinity, this.abortController.signal);
        this.heartbeatInterval = setInterval(() => {
            this.components.connectionManager.getConnections().forEach(conn => {
                Promise.resolve().then(async () => {
                    let start = Date.now();
                    try {
                        const signal = this.timeout.getTimeoutSignal({
                            signal: this.abortController?.signal
                        });
                        const stream = await conn.newStream(this.protocol, {
                            signal,
                            runOnLimitedConnection: true
                        });
                        const bs = byteStream(stream);
                        start = Date.now();
                        await Promise.all([
                            bs.write(randomBytes(PING_LENGTH), {
                                signal
                            }),
                            bs.read({
                                bytes: PING_LENGTH,
                                signal
                            })
                        ]);
                        conn.rtt = Date.now() - start;
                        await bs.unwrap().close({
                            signal
                        });
                    }
                    catch (err) {
                        if (err.name !== 'UnsupportedProtocolError') {
                            throw err;
                        }
                        // protocol was unsupported, but that's ok as it means the remote
                        // peer was still alive. We ran multistream-select which means two
                        // round trips (e.g. 1x for the mss header, then another for the
                        // protocol) so divide the time it took by two
                        conn.rtt = (Date.now() - start) / 2;
                    }
                })
                    .catch(err => {
                    this.log.error('error during heartbeat', err);
                    if (this.abortConnectionOnPingFailure) {
                        this.log.error('aborting connection due to ping failure');
                        conn.abort(err);
                    }
                    else {
                        this.log('connection ping failed, but not aborting due to abortConnectionOnPingFailure flag');
                    }
                });
            });
        }, this.pingIntervalMs);
    }
    stop() {
        this.abortController?.abort();
        if (this.heartbeatInterval != null) {
            clearInterval(this.heartbeatInterval);
        }
    }
}
//# sourceMappingURL=connection-monitor.js.map