From 8bc0b09d256efdeb9db2feb7866ca14e9ae9895a Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 10 Dec 2021 17:25:33 +0000 Subject: [PATCH] feat: convert to typescript Converts this module to typescript Depends on: - [ ] https://github.com/libp2p/js-libp2p-interfaces/pull/116 BREAKING CHANGE: this module now uses the updated libp2p-interfaces module --- .aegir.js => .aegir.cjs | 0 README.md | 22 +- package.json | 37 ++- src/{constants.js => constants.ts} | 8 +- src/{index.js => index.ts} | 123 ++++---- src/listener.js | 156 ---------- src/listener.ts | 172 +++++++++++ src/{socket-to-conn.js => socket-to-conn.ts} | 82 +++--- src/utils.js | 78 ----- src/utils.ts | 44 +++ ...{compliance.spec.js => compliance.spec.ts} | 31 +- ...{connection.spec.js => connection.spec.ts} | 36 +-- test/{filter.spec.js => filter.spec.ts} | 16 +- test/listen-dial.spec.js | 278 ------------------ test/listen-dial.spec.ts | 249 ++++++++++++++++ tsconfig.json | 7 +- 16 files changed, 635 insertions(+), 704 deletions(-) rename .aegir.js => .aegir.cjs (100%) rename src/{constants.js => constants.ts} (53%) rename src/{index.js => index.ts} (53%) delete mode 100644 src/listener.js create mode 100644 src/listener.ts rename src/{socket-to-conn.js => socket-to-conn.ts} (58%) delete mode 100644 src/utils.js create mode 100644 src/utils.ts rename test/{compliance.spec.js => compliance.spec.ts} (60%) rename test/{connection.spec.js => connection.spec.ts} (67%) rename test/{filter.spec.js => filter.spec.ts} (78%) delete mode 100644 test/listen-dial.spec.js create mode 100644 test/listen-dial.spec.ts diff --git a/.aegir.js b/.aegir.cjs similarity index 100% rename from .aegir.js rename to .aegir.cjs diff --git a/README.md b/README.md index eab0735..3399c9e 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,18 @@ > JavaScript implementation of the TCP module for libp2p. It exposes the [interface-transport](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/transport) for dial/listen. `libp2p-tcp` is a very thin shim that adds support for dialing to a `multiaddr`. This small shim will enable libp2p to use other transports. -## Lead Maintainer - -[Jacob Heun](https://github.com/jacobheun) - ## Table of Contents -- [Install](#install) - - [npm](#npm) -- [Usage](#usage) -- [API](#api) -- [Contribute](#contribute) -- [License](#license) +- [js-libp2p-tcp](#js-libp2p-tcp) + - [Table of Contents](#table-of-contents) + - [Install](#install) + - [npm](#npm) + - [Usage](#usage) + - [API](#api) + - [Transport](#transport) + - [Connection](#connection) + - [Contribute](#contribute) + - [License](#license) ## Install @@ -52,7 +52,7 @@ const upgrader = { const tcp = new TCP({ upgrader }) -const listener = tcp.createListener((socket) => { +const listener = tcp.createListener({}, (socket) => { console.log('new connection opened') pipe( ['hello'], diff --git a/package.json b/package.json index a1c61e2..1e08772 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,24 @@ "name": "libp2p-tcp", "version": "0.17.2", "description": "Node.js implementation of the TCP module that libp2p uses, which implements the interface-connection and interface-transport interfaces", - "leadMaintainer": "Jacob Heun ", - "main": "src/index.js", + "main": "./dist/src/index.js", + "type": "module", + "exports": { + ".": { + "import": "./dist/src/index.js" + } + }, "scripts": { "lint": "aegir ts -p check && aegir lint", - "build": "aegir build", - "test": "aegir test -t node", - "test:node": "aegir test -t node", - "release": "aegir release -t node", - "release-minor": "aegir release -t node --type minor", - "release-major": "aegir-release -t node --type major", + "build": "tsc", + "pretest": "npm run build", + "test": "aegir test -t node -f ./dist/test/*.js", + "test:node": "aegir test -t node -f ./dist/test/*.js", + "release": "aegir release -t node -f ./dist/test/*.js", + "release-minor": "aegir release -t node -f ./dist/test/*.js --type minor", + "release-major": "aegir-release -t node -f ./dist/test/*.js --type major", "coverage": "nyc --reporter=text --reporter=lcov npm run test:node" }, - "pre-push": [ - "lint" - ], "repository": { "type": "git", "url": "https://github.com/libp2p/js-libp2p-tcp.git" @@ -41,11 +44,12 @@ "types": "dist/src/index.d.ts", "devDependencies": { "@types/debug": "^4.1.5", - "aegir": "^35.0.3", + "@types/mocha": "^9.0.0", + "aegir": "^36.0.0", "it-pipe": "^1.1.0", - "libp2p-interfaces": "^1.0.0", - "libp2p-interfaces-compliance-tests": "^1.0.0", - "sinon": "^11.1.1", + "libp2p-interfaces": "^2.0.0", + "libp2p-interfaces-compliance-tests": "^2.0.0", + "sinon": "^12.0.0", "streaming-iterables": "^6.0.0" }, "dependencies": { @@ -56,7 +60,8 @@ "libp2p-utils": "^0.4.0", "mafmt": "^10.0.0", "multiaddr": "^10.0.0", - "stream-to-it": "^0.2.2" + "stream-to-it": "^0.2.2", + "why-is-node-running": "^2.2.0" }, "contributors": [ "David Dias ", diff --git a/src/constants.js b/src/constants.ts similarity index 53% rename from src/constants.js rename to src/constants.ts index b7ab8fe..75362ae 100644 --- a/src/constants.js +++ b/src/constants.ts @@ -1,8 +1,6 @@ -'use strict' - // p2p multi-address code -exports.CODE_P2P = 421 -exports.CODE_CIRCUIT = 290 +export const CODE_P2P = 421 +export const CODE_CIRCUIT = 290 // Time to wait for a connection to close gracefully before destroying it manually -exports.CLOSE_TIMEOUT = 2000 +export const CLOSE_TIMEOUT = 2000 diff --git a/src/index.js b/src/index.ts similarity index 53% rename from src/index.js rename to src/index.ts index 7d3f0fd..23f545d 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,17 +1,18 @@ -'use strict' - -const net = require('net') -const mafmt = require('mafmt') -// Missing Type -// @ts-ignore -const withIs = require('class-is') -const errCode = require('err-code') -const log = require('debug')('libp2p:tcp') -const toConnection = require('./socket-to-conn') -const createListener = require('./listener') -const { multiaddrToNetConfig } = require('./utils') -const { AbortError } = require('abortable-iterator') -const { CODE_CIRCUIT, CODE_P2P } = require('./constants') +import net from 'net' +import mafmt from 'mafmt' +import errCode from 'err-code' +import debug from 'debug' +import { toConnection } from './socket-to-conn.js' +import { createListener } from './listener.js' +import { multiaddrToNetConfig } from './utils.js' +import { AbortError } from 'abortable-iterator' +import { CODE_CIRCUIT, CODE_P2P } from './constants.js' +import type { Transport, Upgrader } from 'libp2p-interfaces/transport' +import type { Connection } from 'libp2p-interfaces/connection' +import type { Multiaddr } from 'multiaddr' +import type { Socket } from 'net' + +const log = debug('libp2p:tcp') /** * @typedef {import('multiaddr').Multiaddr} Multiaddr @@ -21,28 +22,28 @@ const { CODE_CIRCUIT, CODE_P2P } = require('./constants') * @typedef {import('net').Socket} Socket */ -class TCP { - /** - * @class - * @param {object} options - * @param {Upgrader} options.upgrader - */ - constructor ({ upgrader }) { - if (!upgrader) { +interface TCPOptions { + upgrader: Upgrader +} + +interface DialOptions { + signal?: AbortSignal +} + +export default class TCP implements Transport { + private readonly _upgrader: Upgrader + + constructor (options: TCPOptions) { + const { upgrader } = options + + if (upgrader == null) { throw new Error('An upgrader must be provided. See https://github.com/libp2p/interface-transport#upgrader.') } + this._upgrader = upgrader } - /** - * @async - * @param {Multiaddr} ma - * @param {object} options - * @param {AbortSignal} [options.signal] - Used to abort dial requests - * @returns {Promise} An upgraded Connection - */ - async dial (ma, options) { - options = options || {} + async dial (ma: Multiaddr, options: DialOptions = {}) { const socket = await this._connect(ma, options) const maConn = toConnection(socket, { remoteAddr: ma, signal: options.signal }) log('new outbound connection %s', maConn.remoteAddr) @@ -51,32 +52,27 @@ class TCP { return conn } - /** - * @private - * @param {Multiaddr} ma - * @param {object} options - * @param {AbortSignal} [options.signal] - Used to abort dial requests - * @returns {Promise} Resolves a TCP Socket - */ - _connect (ma, options = {}) { - if (options.signal && options.signal.aborted) { + async _connect (ma: Multiaddr, options: DialOptions = {}) { + if (options.signal?.aborted === true) { throw new AbortError() } - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const start = Date.now() const cOpts = multiaddrToNetConfig(ma) log('dialing %j', cOpts) const rawSocket = net.connect(cOpts) - const onError = /** @param {Error} err */ err => { + const onError = (err: Error) => { err.message = `connection error ${cOpts.host}:${cOpts.port}: ${err.message}` + done(err) } const onTimeout = () => { log('connection timeout %s:%s', cOpts.host, cOpts.port) + const err = errCode(new Error(`connection timeout after ${Date.now() - start}ms`), 'ERR_CONNECT_TIMEOUT') // Note: this will result in onError() being called rawSocket.emit('error', err) @@ -93,20 +89,29 @@ class TCP { done(new AbortError()) } - const done = /** @param {Error} [err] */ err => { + const done = (err?: any) => { rawSocket.removeListener('error', onError) rawSocket.removeListener('timeout', onTimeout) rawSocket.removeListener('connect', onConnect) - options.signal && options.signal.removeEventListener('abort', onAbort) - if (err) return reject(err) + if (options.signal != null) { + options.signal.removeEventListener('abort', onAbort) + } + + if (err != null) { + return reject(err) + } + resolve(rawSocket) } rawSocket.on('error', onError) rawSocket.on('timeout', onTimeout) rawSocket.on('connect', onConnect) - options.signal && options.signal.addEventListener('abort', onAbort) + + if (options.signal != null) { + options.signal.addEventListener('abort', onAbort) + } }) } @@ -114,31 +119,15 @@ class TCP { * Creates a TCP listener. The provided `handler` function will be called * anytime a new incoming Connection has been successfully upgraded via * `upgrader.upgradeInbound`. - * - * @param {* | function(Connection):void} options - * @param {function(Connection):void} [handler] - * @returns {Listener} A TCP listener */ - createListener (options, handler) { - let listenerHandler - - if (typeof options === 'function') { - listenerHandler = options - options = {} - } else { - listenerHandler = handler - } - options = options || {} - return createListener({ handler: listenerHandler, upgrader: this._upgrader }, options) + createListener (options: {}, handler?: (connection: Connection) => void) { + return createListener({ handler: handler, upgrader: this._upgrader }) } /** * Takes a list of `Multiaddr`s and returns only valid TCP addresses - * - * @param {Multiaddr[]} multiaddrs - * @returns {Multiaddr[]} Valid TCP multiaddrs */ - filter (multiaddrs) { + filter (multiaddrs: Multiaddr[]) { multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] return multiaddrs.filter(ma => { @@ -150,7 +139,3 @@ class TCP { }) } } - -const TCPWithIs = withIs(TCP, { className: 'TCP', symbolName: '@libp2p/js-libp2p-tcp/tcp' }) - -exports = module.exports = TCPWithIs diff --git a/src/listener.js b/src/listener.js deleted file mode 100644 index 30a3c28..0000000 --- a/src/listener.js +++ /dev/null @@ -1,156 +0,0 @@ -'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) -} diff --git a/src/listener.ts b/src/listener.ts new file mode 100644 index 0000000..05ecf78 --- /dev/null +++ b/src/listener.ts @@ -0,0 +1,172 @@ +import net from 'net' +import { EventEmitter } from 'events' +import debug from 'debug' +import { toConnection } from './socket-to-conn.js' +import { CODE_P2P } from './constants.js' +import { + getMultiaddrs, + multiaddrToNetConfig +} from './utils.js' +import type { Connection } from 'libp2p-interfaces/connection' +import type { MultiaddrConnection, Upgrader, Listener } from 'libp2p-interfaces/transport' +import type { Server } from 'net' +import type { Multiaddr } from 'multiaddr' + +const log = Object.assign( + debug('libp2p:tcp:listener'), + { error: debug('libp2p:tcp:listener:error') }) + +interface ServerWithMultiaddrConnections extends Server { + __connections: MultiaddrConnection[] +} + +/** + * Attempts to close the given maConn. If a failure occurs, it will be logged + */ +async function attemptClose (maConn: MultiaddrConnection) { + try { + await maConn.close() + } catch (err) { + log.error('an error occurred closing the connection', err) + } +} + +interface Context { + handler?: (conn: Connection) => void + upgrader: Upgrader +} + +/** + * Create listener + */ +export function createListener (context: Context) { + const { + handler, upgrader + } = context + + let peerId: string | null + let listeningAddr: Multiaddr + + const server: ServerWithMultiaddrConnections = Object.assign(net.createServer(socket => { + // Avoid uncaught errors caused by unstable connections + socket.on('error', err => { + log('socket error', err) + }) + + let maConn: MultiaddrConnection + try { + maConn = toConnection(socket, { listeningAddr }) + } catch (err) { + log.error('inbound connection failed', err) + return + } + + log('new inbound connection %s', maConn.remoteAddr) + try { + upgrader.upgradeInbound(maConn) + .then((conn) => { + log('inbound connection %s upgraded', maConn.remoteAddr) + + trackConn(server, maConn) + + if (handler != null) { + handler(conn) + } + + listener.emit('connection', conn) + }) + .catch(async err => { + log.error('inbound connection failed', err) + + await attemptClose(maConn) + }) + .catch(err => { + log.error('closing inbound connection failed', err) + }) + } catch (err) { + log.error('inbound connection failed', err) + + attemptClose(maConn) + .catch(err => { + log.error('closing inbound connection failed', err) + }) + } + }), + // Keep track of open connections to destroy in case of timeout + { __connections: [] }) + + const listener: Listener = Object.assign(new EventEmitter(), { + getAddrs: () => { + let addrs: Multiaddr[] = [] + const address = server.address() + + if (address == null) { + throw new Error('Listener is not ready yet') + } + + if (typeof address === 'string') { + throw new Error('Incorrect server address type') + } + + // 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 != null ? ma.encapsulate(`/p2p/${peerId}`) : ma) + }, + listen: async (ma: Multiaddr) => { + listeningAddr = ma + peerId = ma.getPeerId() + + if (peerId == null) { + listeningAddr = ma.decapsulateCode(CODE_P2P) + } + + return await new Promise((resolve, reject) => { + const options = multiaddrToNetConfig(listeningAddr) + server.listen(options, (err?: any) => { + if (err != null) { + return reject(err) + } + log('Listening on %s', server.address()) + resolve() + }) + }) + }, + close: async () => { + if (!server.listening) { + return + } + + await Promise.all([ + server.__connections.map(async maConn => await attemptClose(maConn)) + ]) + + await new Promise((resolve, reject) => { + server.close(err => (err != null) ? reject(err) : resolve()) + }) + } + }) + + server + .on('listening', () => listener.emit('listening')) + .on('error', err => listener.emit('error', err)) + .on('close', () => listener.emit('close')) + + return listener +} + +function trackConn (server: ServerWithMultiaddrConnections, maConn: MultiaddrConnection) { + server.__connections.push(maConn) + + const untrackConn = () => { + server.__connections = server.__connections.filter(c => c !== maConn) + } + + // @ts-expect-error + maConn.conn.once('close', untrackConn) +} diff --git a/src/socket-to-conn.js b/src/socket-to-conn.ts similarity index 58% rename from src/socket-to-conn.js rename to src/socket-to-conn.ts index 9fd4a68..7f255fb 100644 --- a/src/socket-to-conn.js +++ b/src/socket-to-conn.ts @@ -1,52 +1,43 @@ -'use strict' - -const abortable = require('abortable-iterator') -const log = require('debug')('libp2p:tcp:socket') -// Missing Type -// @ts-ignore -const toIterable = require('stream-to-it') -const toMultiaddr = require('libp2p-utils/src/ip-port-to-multiaddr') -const { CLOSE_TIMEOUT } = require('./constants') - -/** - * @typedef {import('multiaddr').Multiaddr} Multiaddr - * @typedef {import('libp2p-interfaces/src/transport/types').MultiaddrConnection} MultiaddrConnection - * @typedef {import('net').Socket} Socket - */ +import abortable from 'abortable-iterator' +import debug from 'debug' +// @ts-expect-error no types +import toIterable from 'stream-to-it' +import toMultiaddr from 'libp2p-utils/src/ip-port-to-multiaddr.js' +import { CLOSE_TIMEOUT } from './constants.js' +import type { Socket } from 'net' +import type { Multiaddr } from 'multiaddr' +import type { MultiaddrConnection } from 'libp2p-interfaces/transport' + +const log = debug('libp2p:tcp:socket') + +interface ToConnectionOptions { + listeningAddr?: Multiaddr + remoteAddr?: Multiaddr + localAddr?: Multiaddr + signal?: AbortSignal +} /** * Convert a socket into a MultiaddrConnection * https://github.com/libp2p/interface-transport#multiaddrconnection - * - * @private - * @param {Socket} socket - * @param {object} options - * @param {Multiaddr} [options.listeningAddr] - * @param {Multiaddr} [options.remoteAddr] - * @param {Multiaddr} [options.localAddr] - * @param {AbortSignal} [options.signal] - * @returns {MultiaddrConnection} */ -const toConnection = (socket, options) => { - options = options || {} +export const toConnection = (socket: Socket, options?: ToConnectionOptions) => { + options = options ?? {} // Check if we are connected on a unix path - if (options.listeningAddr && options.listeningAddr.getPath()) { + if (options.listeningAddr?.getPath() != null) { options.remoteAddr = options.listeningAddr } - if (options.remoteAddr && options.remoteAddr.getPath()) { + if (options.remoteAddr?.getPath() != null) { options.localAddr = options.remoteAddr } const { sink, source } = toIterable.duplex(socket) - /** @type {MultiaddrConnection} */ - const maConn = { + const maConn: MultiaddrConnection = { async sink (source) { - if (options.signal) { - // Missing Type for "abortable" - // @ts-ignore + if ((options?.signal) != null) { source = abortable(source, options.signal) } @@ -55,11 +46,10 @@ const toConnection = (socket, options) => { for await (const chunk of source) { // Convert BufferList to Buffer // Sink in StreamMuxer define argument as Uint8Array so chunk type infers as number which can't be sliced - // @ts-ignore yield Buffer.isBuffer(chunk) ? chunk : chunk.slice() } })()) - } catch (err) { + } catch (err: any) { // If aborted we can safely ignore if (err.type !== 'aborted') { // If the source errored the socket will already have been destroyed by @@ -71,22 +61,21 @@ const toConnection = (socket, options) => { }, // Missing Type for "abortable" - // @ts-ignore - source: options.signal ? abortable(source, options.signal) : source, + source: (options.signal != null) ? abortable(source, options.signal) : source, conn: socket, - localAddr: options.localAddr || toMultiaddr(socket.localAddress, socket.localPort), + localAddr: options.localAddr ?? toMultiaddr(socket.localAddress ?? '', socket.localPort ?? ''), // If the remote address was passed, use it - it may have the peer ID encapsulated - remoteAddr: options.remoteAddr || toMultiaddr(socket.remoteAddress || '', socket.remotePort || ''), + remoteAddr: options.remoteAddr ?? toMultiaddr(socket.remoteAddress ?? '', socket.remotePort ?? ''), timeline: { open: Date.now() }, async close () { if (socket.destroyed) return - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const start = Date.now() // Attempt to end the socket. If it takes longer to close than the @@ -107,15 +96,18 @@ const toConnection = (socket, options) => { } resolve() - }, CLOSE_TIMEOUT) + }, CLOSE_TIMEOUT).unref() socket.once('close', () => { clearTimeout(timeout) resolve() }) - socket.end(/** @param {Error} [err] */(err) => { + socket.end((err?: Error & { code?: string }) => { + clearTimeout(timeout) maConn.timeline.close = Date.now() - if (err) return reject(err) + if (err != null) { + return reject(err) + } resolve() }) }) @@ -126,12 +118,10 @@ const toConnection = (socket, options) => { // In instances where `close` was not explicitly called, // such as an iterable stream ending, ensure we have set the close // timeline - if (!maConn.timeline.close) { + if (maConn.timeline.close == null) { maConn.timeline.close = Date.now() } }) return maConn } - -module.exports = toConnection diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 1ba1ef9..0000000 --- a/src/utils.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict' - -const { Multiaddr } = require('multiaddr') -const os = require('os') -const { resolve } = require('path') -const ProtoFamily = { ip4: 'IPv4', ip6: 'IPv6' } - -/** - * @typedef {import('multiaddr').MultiaddrObject} MultiaddrObject - */ - -/** - * @param {Multiaddr} addr - * @returns {MultiaddrObject} - */ -function multiaddrToNetConfig (addr) { - const listenPath = addr.getPath() - // unix socket listening - if (listenPath) { - // TCP should not return unix socket else need to refactor listener which accepts connection options object - // @ts-ignore - return resolve(listenPath) - } - // tcp listening - return addr.toOptions() -} - -/** - * @param {'ip4' | 'ip6'} proto - * @param {string} ip - * @param {number} port - * @returns {Multiaddr[]} - */ -function getMultiaddrs (proto, ip, port) { - const toMa = /** @param {string} ip */ ip => new Multiaddr(`/${proto}/${ip}/tcp/${port}`) - return (isAnyAddr(ip) ? getNetworkAddrs(ProtoFamily[proto]) : [ip]).map(toMa) -} - -/** - * @param {string} ip - * @returns {boolean} - */ -function isAnyAddr (ip) { - return ['0.0.0.0', '::'].includes(ip) -} - -/** - * @private - * @param {string} family - One of ['IPv6', 'IPv4'] - * @returns {string[]} an array of ip address strings - */ -const networks = os.networkInterfaces() - -/** - * @param {string} family - * @returns {string[]} - */ -function getNetworkAddrs (family) { - const addresses = [] - - for (const [, netAddrs] of Object.entries(networks)) { - if (netAddrs) { - for (const netAddr of netAddrs) { - if (netAddr.family === family) { - addresses.push(netAddr.address) - } - } - } - } - - return addresses -} - -module.exports = { - multiaddrToNetConfig, - isAnyAddr, - getMultiaddrs -} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..a88c52c --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,44 @@ +import { Multiaddr } from 'multiaddr' +import os from 'os' + +const ProtoFamily = { ip4: 'IPv4', ip6: 'IPv6' } + +export function multiaddrToNetConfig (addr: Multiaddr) { + const listenPath = addr.getPath() + + // unix socket listening + if (listenPath != null) { + // TCP should not return unix socket else need to refactor listener which accepts connection options object + throw new Error('Unix Sockets are not supported by the TCP transport') + } + + // tcp listening + return addr.toOptions() +} + +export function getMultiaddrs (proto: 'ip4' | 'ip6', ip: string, port: number) { + const toMa = (ip: string) => new Multiaddr(`/${proto}/${ip}/tcp/${port}`) + return (isAnyAddr(ip) ? getNetworkAddrs(ProtoFamily[proto]) : [ip]).map(toMa) +} + +export function isAnyAddr (ip: string) { + return ['0.0.0.0', '::'].includes(ip) +} + +const networks = os.networkInterfaces() + +function getNetworkAddrs (family: string) { + const addresses = [] + + for (const [, netAddrs] of Object.entries(networks)) { + if (netAddrs != null) { + for (const netAddr of netAddrs) { + if (netAddr.family === family) { + addresses.push(netAddr.address) + } + } + } + } + + return addresses +} diff --git a/test/compliance.spec.js b/test/compliance.spec.ts similarity index 60% rename from test/compliance.spec.js rename to test/compliance.spec.ts index 975b016..1c3c091 100644 --- a/test/compliance.spec.js +++ b/test/compliance.spec.ts @@ -1,15 +1,18 @@ -/* eslint-env mocha */ -'use strict' - -const sinon = require('sinon') -const tests = require('libp2p-interfaces-compliance-tests/src/transport') -const { Multiaddr } = require('multiaddr') -const net = require('net') -const TCP = require('../src') +import sinon from 'sinon' +import tests from 'libp2p-interfaces-compliance-tests/transport' +import { Multiaddr } from 'multiaddr' +import net from 'net' +import TCP from '../src/index.js' describe('interface-transport compliance', () => { tests({ - setup ({ upgrader }) { + async setup (args) { + if (args == null) { + throw new Error('No args') + } + + const { upgrader } = args + const tcp = new TCP({ upgrader }) const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/9091'), @@ -19,14 +22,15 @@ describe('interface-transport compliance', () => { // Used by the dial tests to simulate a delayed connect const connector = { - delay (delayMs) { + delay (delayMs: number) { const netConnect = net.connect - sinon.replace(net, 'connect', (opts) => { + sinon.replace(net, 'connect', (opts: any) => { const socket = netConnect(opts) const socketEmit = socket.emit.bind(socket) - sinon.replace(socket, 'emit', (...args) => { + sinon.replace(socket, 'emit', (...args: [string]) => { const time = args[0] === 'connect' ? delayMs : 0 setTimeout(() => socketEmit(...args), time) + return true }) return socket }) @@ -37,6 +41,7 @@ describe('interface-transport compliance', () => { } return { transport: tcp, addrs, connector } - } + }, + async teardown () {} }) }) diff --git a/test/connection.spec.js b/test/connection.spec.ts similarity index 67% rename from test/connection.spec.js rename to test/connection.spec.ts index baf5739..4da6f65 100644 --- a/test/connection.spec.js +++ b/test/connection.spec.ts @@ -1,33 +1,27 @@ -/* eslint-env mocha */ -'use strict' - -const { expect } = require('aegir/utils/chai') -const TCP = require('../src') -const { Multiaddr } = require('multiaddr') +import { expect } from 'aegir/utils/chai.js' +import TCP from '../src/index.js' +import { Multiaddr } from 'multiaddr' +import { mockUpgrader } from 'libp2p-interfaces-compliance-tests/transport/utils' +import type { Connection } from 'libp2p-interfaces/connection' describe('valid localAddr and remoteAddr', () => { - let tcp - - const mockUpgrader = { - upgradeInbound: maConn => maConn, - upgradeOutbound: maConn => maConn - } + let tcp: TCP beforeEach(() => { - tcp = new TCP({ upgrader: mockUpgrader }) + tcp = new TCP({ upgrader: mockUpgrader() }) }) const ma = new Multiaddr('/ip4/127.0.0.1/tcp/0') it('should resolve port 0', async () => { // Create a Promise that resolves when a connection is handled - let handled - const handlerPromise = new Promise(resolve => { handled = resolve }) + let handled: (conn: Connection) => void + const handlerPromise = new Promise(resolve => { handled = resolve }) - const handler = conn => handled(conn) + const handler = (conn: Connection) => handled(conn) // Create a listener with the handler - const listener = tcp.createListener(handler) + const listener = tcp.createListener({}, handler) // Listen on the multi-address await listener.listen(ma) @@ -53,13 +47,13 @@ describe('valid localAddr and remoteAddr', () => { it('should handle multiple simultaneous closes', async () => { // Create a Promise that resolves when a connection is handled - let handled - const handlerPromise = new Promise(resolve => { handled = resolve }) + let handled: (conn: Connection) => void + const handlerPromise = new Promise(resolve => { handled = resolve }) - const handler = conn => handled(conn) + const handler = (conn: Connection) => handled(conn) // Create a listener with the handler - const listener = tcp.createListener(handler) + const listener = tcp.createListener({}, handler) // Listen on the multi-address await listener.listen(ma) diff --git a/test/filter.spec.js b/test/filter.spec.ts similarity index 78% rename from test/filter.spec.js rename to test/filter.spec.ts index c4a7553..0778945 100644 --- a/test/filter.spec.js +++ b/test/filter.spec.ts @@ -1,18 +1,16 @@ -/* eslint-env mocha */ -'use strict' - -const { expect } = require('aegir/utils/chai') -const TCP = require('../src') -const { Multiaddr } = require('multiaddr') +import { expect } from 'aegir/utils/chai.js' +import TCP from '../src/index.js' +import { Multiaddr } from 'multiaddr' +import { mockUpgrader } from 'libp2p-interfaces-compliance-tests/transport/utils' describe('filter addrs', () => { const base = '/ip4/127.0.0.1' const ipfs = '/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw' - let tcp + let tcp: TCP before(() => { - tcp = new TCP({ upgrader: {} }) + tcp = new TCP({ upgrader: mockUpgrader() }) }) it('filter valid addrs for this transport', () => { @@ -34,7 +32,7 @@ describe('filter addrs', () => { it('filter a single addr for this transport', () => { const ma1 = new Multiaddr(base + '/tcp/9090') - const valid = tcp.filter(ma1) + const valid = tcp.filter([ma1]) expect(valid.length).to.equal(1) expect(valid[0]).to.eql(ma1) }) diff --git a/test/listen-dial.spec.js b/test/listen-dial.spec.js deleted file mode 100644 index 8af2a91..0000000 --- a/test/listen-dial.spec.js +++ /dev/null @@ -1,278 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const { expect } = require('aegir/utils/chai') -const TCP = require('../src') -const net = require('net') -const os = require('os') -const path = require('path') -const { Multiaddr } = require('multiaddr') -const pipe = require('it-pipe') -const { collect, map } = require('streaming-iterables') -const isCI = process.env.CI -const isWindows = os.platform() === 'win32' - -const skipOnWindows = isWindows ? it.skip : it - -describe('construction', () => { - it('requires an upgrader', () => { - expect(() => new TCP()).to.throw() - }) -}) - -describe('listen', () => { - let tcp - let listener - - beforeEach(() => { - tcp = new TCP({ - upgrader: { - upgradeOutbound: maConn => maConn, - upgradeInbound: maConn => maConn - } - }) - }) - afterEach(async () => { - listener && await listener.close() - }) - - it('close listener with connections, through timeout', async () => { - const mh = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - listener = tcp.createListener((conn) => { - pipe(conn, conn) - }) - - await listener.listen(mh) - - const socket1 = net.connect(9090) - const socket2 = net.connect(9090) - - socket1.write('Some data that is never handled') - socket1.end() - socket1.on('error', () => {}) - socket2.on('error', () => {}) - - await new Promise((resolve) => { - socket1.on('connect', async () => { - await listener.close({ timeout: 100 }) - resolve() - }) - }) - }) - - // Windows doesn't support unix paths - skipOnWindows('listen on path', async () => { - const mh = new Multiaddr(`/unix${path.resolve(os.tmpdir(), '/tmp/p2pd.sock')}`) - - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - }) - - it('listen on port 0', async () => { - const mh = new Multiaddr('/ip4/127.0.0.1/tcp/0') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - }) - - it('listen on IPv6 addr', async () => { - if (isCI) { - return - } - const mh = new Multiaddr('/ip6/::/tcp/9090') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - }) - - it('listen on any Interface', async () => { - const mh = new Multiaddr('/ip4/0.0.0.0/tcp/9090') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - }) - - it('getAddrs', async () => { - const mh = new Multiaddr('/ip4/127.0.0.1/tcp/9090') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - - const multiaddrs = listener.getAddrs() - expect(multiaddrs.length).to.equal(1) - expect(multiaddrs[0]).to.deep.equal(mh) - }) - - it('getAddrs on port 0 listen', async () => { - const mh = new Multiaddr('/ip4/127.0.0.1/tcp/0') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - - const multiaddrs = listener.getAddrs() - expect(multiaddrs.length).to.equal(1) - }) - - it('getAddrs from listening on 0.0.0.0', async () => { - const mh = new Multiaddr('/ip4/0.0.0.0/tcp/9090') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - - const multiaddrs = listener.getAddrs() - expect(multiaddrs.length > 0).to.equal(true) - expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1) - }) - - it('getAddrs from listening on 0.0.0.0 and port 0', async () => { - const mh = new Multiaddr('/ip4/0.0.0.0/tcp/0') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - - const multiaddrs = listener.getAddrs() - expect(multiaddrs.length > 0).to.equal(true) - expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1) - }) - - it('getAddrs preserves IPFS Id', async () => { - const mh = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - listener = tcp.createListener((conn) => {}) - await listener.listen(mh) - - const multiaddrs = listener.getAddrs() - expect(multiaddrs.length).to.equal(1) - expect(multiaddrs[0]).to.deep.equal(mh) - }) -}) - -describe('dial', () => { - let tcp - let listener - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090') - - beforeEach(async () => { - tcp = new TCP({ - upgrader: { - upgradeOutbound: maConn => maConn, - upgradeInbound: maConn => maConn - } - }) - listener = tcp.createListener((conn) => { - pipe( - conn, - map((x) => Buffer.from(x.toString() + '!')), - conn - ) - }) - await listener.listen(ma) - }) - - afterEach(() => listener.close()) - - it('dial on IPv4', async () => { - const values = await pipe( - ['hey'], - await tcp.dial(ma), - collect - ) - expect(values).to.eql([Buffer.from('hey!')]) - }) - - it('dial on IPv6', async () => { - if (isCI) { - return - } - - const ma = new Multiaddr('/ip6/::/tcp/9066') - const listener = tcp.createListener((conn) => { - pipe(conn, conn) - }) - await listener.listen(ma) - - const values = await pipe( - ['hey'], - await tcp.dial(ma), - collect - ) - expect(values).to.be.eql([Buffer.from('hey')]) - - await listener.close() - }) - - // Windows doesn't support unix paths - skipOnWindows('dial on path', async () => { - const ma = new Multiaddr(`/unix${path.resolve(os.tmpdir(), '/tmp/p2pd.sock')}`) - - const listener = tcp.createListener((conn) => { - pipe(conn, conn) - }) - await listener.listen(ma) - - const connection = await tcp.dial(ma) - - const values = await pipe( - ['hey'], - connection, - collect - ) - - expect(values).to.be.eql([Buffer.from('hey')]) - await listener.close() - }) - - it('dial and destroy on listener', async () => { - let handled - const handledPromise = new Promise((resolve) => { - handled = resolve - }) - - const ma = new Multiaddr('/ip6/::/tcp/0') - - const listener = tcp.createListener(async (conn) => { - await pipe( - [], - conn - ) - handled() - }) - - await listener.listen(ma) - const addrs = listener.getAddrs() - await pipe(await tcp.dial(addrs[0])) - - await handledPromise - await listener.close() - }) - - it('dial and destroy on dialer', async () => { - if (isCI) { - return - } - - let handled - const handledPromise = new Promise((resolve) => { - handled = resolve - }) - - const ma = new Multiaddr('/ip6/::/tcp/0') - - const listener = tcp.createListener(async (conn) => { - // pull(conn, pull.onEnd(destroyed)) - await pipe(conn) - handled() - }) - - await listener.listen(ma) - const addrs = listener.getAddrs() - await pipe(await tcp.dial(addrs[0])) - - await handledPromise - await listener.close() - }) - - it('dial on IPv4 with IPFS Id', async () => { - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const conn = await tcp.dial(ma) - - const res = await pipe( - ['hey'], - conn, - collect - ) - expect(res).to.be.eql([Buffer.from('hey!')]) - }) -}) diff --git a/test/listen-dial.spec.ts b/test/listen-dial.spec.ts new file mode 100644 index 0000000..8450a2c --- /dev/null +++ b/test/listen-dial.spec.ts @@ -0,0 +1,249 @@ +import { expect } from 'aegir/utils/chai.js' +import TCP from '../src/index.js' +import os from 'os' +import path from 'path' +import { Multiaddr } from 'multiaddr' +import pipe from 'it-pipe' +import { collect } from 'streaming-iterables' +import { mockUpgrader } from 'libp2p-interfaces-compliance-tests/transport/utils' + +const isCI = process.env.CI + +describe('construction', () => { + it('requires an upgrader', () => { + // @ts-expect-error missing args + expect(() => new TCP()).to.throw() + }) +}) + +describe('listen', () => { + let tcp: TCP + let listener: any + + beforeEach(() => { + tcp = new TCP({ + upgrader: mockUpgrader() + }) + }) + afterEach(async () => { + try { + if (listener != null) { + await listener.close() + } + } catch { + // some tests close the listener so ignore errors + } + }) + + // TCP doesn't support unix paths + it.skip('listen on path', async () => { + const mh = new Multiaddr(`/unix${path.resolve(os.tmpdir(), `/tmp/p2pd-${Date.now()}.sock`)}`) + + listener = tcp.createListener({}) + await listener.listen(mh) + }) + + it('listen on port 0', async () => { + const mh = new Multiaddr('/ip4/127.0.0.1/tcp/0') + listener = tcp.createListener({}) + await listener.listen(mh) + }) + + it('listen on IPv6 addr', async () => { + if (isCI != null) { + return + } + const mh = new Multiaddr('/ip6/::/tcp/9090') + listener = tcp.createListener({}) + await listener.listen(mh) + }) + + it('listen on any Interface', async () => { + const mh = new Multiaddr('/ip4/0.0.0.0/tcp/9090') + listener = tcp.createListener({}) + await listener.listen(mh) + }) + + it('getAddrs', async () => { + const mh = new Multiaddr('/ip4/127.0.0.1/tcp/9090') + listener = tcp.createListener({}) + await listener.listen(mh) + + const multiaddrs = listener.getAddrs() + expect(multiaddrs.length).to.equal(1) + expect(multiaddrs[0]).to.deep.equal(mh) + }) + + it('getAddrs on port 0 listen', async () => { + const mh = new Multiaddr('/ip4/127.0.0.1/tcp/0') + listener = tcp.createListener({}) + await listener.listen(mh) + + const multiaddrs = listener.getAddrs() + expect(multiaddrs.length).to.equal(1) + }) + + it('getAddrs from listening on 0.0.0.0', async () => { + const mh = new Multiaddr('/ip4/0.0.0.0/tcp/9090') + listener = tcp.createListener({}) + await listener.listen(mh) + + const multiaddrs = listener.getAddrs() + expect(multiaddrs.length > 0).to.equal(true) + expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1) + }) + + it('getAddrs from listening on 0.0.0.0 and port 0', async () => { + const mh = new Multiaddr('/ip4/0.0.0.0/tcp/0') + listener = tcp.createListener({}) + await listener.listen(mh) + + const multiaddrs = listener.getAddrs() + expect(multiaddrs.length > 0).to.equal(true) + expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1) + }) + + it('getAddrs preserves IPFS Id', async () => { + const mh = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') + listener = tcp.createListener({}) + await listener.listen(mh) + + const multiaddrs = listener.getAddrs() + expect(multiaddrs.length).to.equal(1) + expect(multiaddrs[0]).to.deep.equal(mh) + }) +}) + +describe('dial', () => { + let tcp: TCP + + beforeEach(async () => { + tcp = new TCP({ + upgrader: mockUpgrader() + }) + }) + + it('dial on IPv4', async () => { + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090') + const listener = tcp.createListener({}) + await listener.listen(ma) + + const conn = await tcp.dial(ma) + const { stream } = await conn.newStream(['/test/stream']) + + const values = await pipe( + ['hey'], + stream, + collect + ) + + expect(values).to.deep.equal(['hey']) + await conn.close() + await listener.close() + }) + + it('dial on IPv6', async () => { + if (isCI != null) { + return + } + + const ma = new Multiaddr('/ip6/::/tcp/9090') + const listener = tcp.createListener({}) + await listener.listen(ma) + const conn = await tcp.dial(ma) + const { stream } = await conn.newStream(['/test/stream']) + + const values = await pipe( + ['hey'], + stream, + collect + ) + expect(values).to.deep.equal(['hey']) + await conn.close() + await listener.close() + }) + + // TCP doesn't support unix paths + it.skip('dial on path', async () => { + const ma = new Multiaddr(`/unix${path.resolve(os.tmpdir(), `/tmp/p2pd-${Date.now()}.sock`)}`) + + const listener = tcp.createListener({}) + await listener.listen(ma) + const conn = await tcp.dial(ma) + const { stream } = await conn.newStream(['/test/stream']) + + const values = await pipe( + ['hey'], + stream, + collect + ) + + expect(values).to.deep.equal(['hey']) + await conn.close() + await listener.close() + }) + + it('dial and destroy on listener', async () => { + let handled: () => void + const handledPromise = new Promise(resolve => { handled = resolve }) + + const ma = new Multiaddr('/ip6/::/tcp/9090') + + const listener = tcp.createListener({}, (conn) => { + conn.close().then(() => handled()).catch(() => {}) + }) + + await listener.listen(ma) + const addrs = listener.getAddrs() + + const conn = await tcp.dial(addrs[0]) + const { stream } = await conn.newStream(['/test/stream']) + await pipe(stream) + + await handledPromise + await conn.close() + await listener.close() + }) + + it('dial and destroy on dialer', async () => { + if (isCI != null) { + return + } + + let handled: () => void + const handledPromise = new Promise(resolve => { handled = resolve }) + + const ma = new Multiaddr('/ip6/::/tcp/9090') + + const listener = tcp.createListener({}, () => { + handled() + }) + + await listener.listen(ma) + const addrs = listener.getAddrs() + const conn = await tcp.dial(addrs[0]) + + await conn.close() + await handledPromise + await listener.close() + }) + + it('dials on IPv4 with IPFS Id', async () => { + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') + const listener = tcp.createListener({}) + await listener.listen(ma) + + const conn = await tcp.dial(ma) + const { stream } = await conn.newStream(['/test/stream']) + + const values = await pipe( + ['hey'], + stream, + collect + ) + expect(values).to.deep.equal(['hey']) + + await conn.close() + await listener.close() + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 3de2a3b..f296f99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,12 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" }, "include": [ - "src" + "src", + "test" ] }