UNPKG

libp2p-tcp

Version:

Node.js implementation of the TCP module that libp2p uses, which implements the interface-connection and interface-transport interfaces

157 lines (133 loc) 4.48 kB
'use strict' const net = require('net') const EventEmitter = require('events') const debug = require('debug') const log = Object.assign( debug('libp2p:tcp:listener'), { error: debug('libp2p:tcp:listener:error') }) const toConnection = require('./socket-to-conn') const { CODE_P2P } = require('./constants') const { getMultiaddrs, multiaddrToNetConfig } = require('./utils') /** * @typedef {import('multiaddr').Multiaddr} Multiaddr * @typedef {import('libp2p-interfaces/src/connection').Connection} Connection * @typedef {import('libp2p-interfaces/src/transport/types').Upgrader} Upgrader * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection * @typedef {import('libp2p-interfaces/src/transport/types').Listener} Listener * @typedef {import('net').Server & {__connections: MultiaddrConnection[]}} Server */ /** * Attempts to close the given maConn. If a failure occurs, it will be logged. * * @private * @param {MultiaddrConnection} maConn */ async function attemptClose (maConn) { try { maConn && await maConn.close() } catch (err) { log.error('an error occurred closing the connection', err) } } /** * Create listener * * @param {object} context * @param {function(Connection):void} context.handler * @param {Upgrader} context.upgrader * @param {*} options * @returns {Listener} */ module.exports = ({ handler, upgrader }, options) => { /** @type {Server} */ // eslint-disable-next-line prefer-const let server /** @type {string | null} */ let peerId /** @type {Multiaddr} */ let listeningAddr const listener = Object.assign(new EventEmitter(), { getAddrs: () => { /** @type {Multiaddr[]} */ let addrs = [] /** @type {import('net').AddressInfo} */ // @ts-ignore const address = server.address() if (!address) { throw new Error('Listener is not ready yet') } // Because TCP will only return the IPv6 version // we need to capture from the passed multiaddr if (listeningAddr.toString().startsWith('/ip4')) { addrs = addrs.concat(getMultiaddrs('ip4', address.address, address.port)) } else if (address.family === 'IPv6') { addrs = addrs.concat(getMultiaddrs('ip6', address.address, address.port)) } return addrs.map(ma => peerId ? ma.encapsulate(`/p2p/${peerId}`) : ma) }, listen: async (/** @type {Multiaddr} */ ma) => { listeningAddr = ma peerId = ma.getPeerId() if (peerId) { listeningAddr = ma.decapsulateCode(CODE_P2P) } return new Promise((resolve, reject) => { const options = multiaddrToNetConfig(listeningAddr) server.listen(options, (/** @type {any} */ err) => { if (err) return reject(err) log('Listening on %s', server.address()) resolve(undefined) }) }) }, close: async () => { if (!server.listening) return return new Promise((resolve, reject) => { server.__connections.forEach(maConn => attemptClose(maConn)) server.close(err => err ? reject(err) : resolve(undefined)) }) } }) server = Object.assign(net.createServer(async socket => { // Avoid uncaught errors caused by unstable connections socket.on('error', err => log('socket error', err)) /** @type {MultiaddrConnection} */ let maConn let conn try { maConn = toConnection(socket, { listeningAddr }) log('new inbound connection %s', maConn.remoteAddr) conn = await upgrader.upgradeInbound(maConn) } catch (err) { log.error('inbound connection failed', err) // @ts-ignore return attemptClose(maConn) } log('inbound connection %s upgraded', maConn.remoteAddr) trackConn(server, maConn) if (handler) handler(conn) listener.emit('connection', conn) }), // Keep track of open connections to destroy in case of timeout { __connections: [] }) server .on('listening', () => listener.emit('listening')) .on('error', err => listener.emit('error', err)) .on('close', () => listener.emit('close')) return listener } /** * @param {Server} server * @param {MultiaddrConnection} maConn */ function trackConn (server, maConn) { server.__connections.push(maConn) const untrackConn = () => { server.__connections = server.__connections.filter(c => c !== maConn) } // @ts-ignore maConn.conn.once('close', untrackConn) }