Files
js-libp2p/src/dialer/dial-request.ts
Alex Potsides 199395de4d feat: convert to typescript (#1172)
Converts this module to typescript.

- Ecosystem modules renamed from (e.g.) `libp2p-tcp` to `@libp2p/tcp`
- Ecosystem module now have named exports
- Configuration has been updated, now pass instances of modules instead of classes:
- Some configuration keys have been renamed to make them more descriptive.  `transport` -> `transports`, `connEncryption` -> `connectionEncryption`.  In general where we pass multiple things, the key is now plural, e.g. `streamMuxer` -> `streamMuxers`, `contentRouting` -> `contentRouters`, etc.  Where we are configuring a singleton the config key is singular, e.g. `connProtector` -> `connectionProtector` etc.
- Properties of the `modules` config key have been moved to the root
- Properties of the `config` config key have been moved to the root
```js
// before
import Libp2p from 'libp2p'
import TCP from 'libp2p-tcp'

await Libp2p.create({
  modules: {
    transport: [
      TCP
    ],
  }
  config: {
    transport: {
      [TCP.tag]: {
        foo: 'bar'
      }
    },
    relay: {
      enabled: true,
      hop: {
        enabled: true,
        active: true
      }
    }
  }
})
```
```js
// after
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'

await createLibp2p({
  transports: [
    new TCP({ foo: 'bar' })
  ],
  relay: {
    enabled: true,
    hop: {
      enabled: true,
      active: true
    }
  }
})
```
- Use of `enabled` flag has been reduced - previously you could pass a module but disable it with config.  Now if you don't want a feature, just don't pass an implementation.   Eg:
```js
// before
await Libp2p.create({
  modules: {
    transport: [
      TCP
    ],
    pubsub: Gossipsub
  },
  config: {
    pubsub: {
      enabled: false
    }
  }
})
```
```js
// after
await createLibp2p({
  transports: [
    new TCP()
  ]
})
```
- `.multiaddrs` renamed to `.getMultiaddrs()` because it's not a property accessor, work is done by that method to calculate announce addresses, observed addresses, etc
- `/p2p/${peerId}` is now appended to all addresses returned by `.getMultiaddrs()` so they can be used opaquely (every consumer has to append the peer ID to the address to actually use it otherwise).  If you need low-level unadulterated addresses, call methods on the address manager.

BREAKING CHANGE: types are no longer hand crafted, this module is now ESM only
2022-03-28 14:30:27 +01:00

113 lines
3.5 KiB
TypeScript

import errCode from 'err-code'
import { anySignal } from 'any-signal'
import FIFO from 'p-fifo'
// @ts-expect-error setMaxListeners is missing from the node 16 types
import { setMaxListeners } from 'events'
import { codes } from '../errors.js'
import { logger } from '@libp2p/logger'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Connection } from '@libp2p/interfaces/connection'
import type { AbortOptions } from '@libp2p/interfaces'
import type { DefaultDialer } from './index.js'
const log = logger('libp2p:dialer:dial-request')
export interface DialAction {
(m: Multiaddr, options: AbortOptions): Promise<Connection>
}
export interface DialRequestOptions {
addrs: Multiaddr[]
dialAction: DialAction
dialer: DefaultDialer
}
export class DialRequest {
private readonly addrs: Multiaddr[]
private readonly dialer: DefaultDialer
private readonly dialAction: DialAction
/**
* Manages running the `dialAction` on multiple provided `addrs` in parallel
* up to a maximum determined by the number of tokens returned
* from `dialer.getTokens`. Once a DialRequest is created, it can be
* started using `DialRequest.run(options)`. Once a single dial has succeeded,
* all other dials in the request will be cancelled.
*/
constructor (options: DialRequestOptions) {
const {
addrs,
dialAction,
dialer
} = options
this.addrs = addrs
this.dialer = dialer
this.dialAction = dialAction
}
async run (options: AbortOptions = {}): Promise<Connection> {
const tokens = this.dialer.getTokens(this.addrs.length)
// If no tokens are available, throw
if (tokens.length < 1) {
throw errCode(new Error('No dial tokens available'), codes.ERR_NO_DIAL_TOKENS)
}
const tokenHolder = new FIFO<number>()
for (const token of tokens) {
void tokenHolder.push(token).catch(err => {
log.error(err)
})
}
const dialAbortControllers = this.addrs.map(() => {
const controller = new AbortController()
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, controller.signal)
} catch {}
return controller
})
if (options.signal != null) {
try {
// fails on node < 15.4
setMaxListeners?.(Infinity, options.signal)
} catch {}
}
let completedDials = 0
try {
return await Promise.any(this.addrs.map(async (addr, i) => {
const token = await tokenHolder.shift() // get token
let conn
try {
const signal = dialAbortControllers[i].signal
conn = await this.dialAction(addr, { ...options, signal: (options.signal != null) ? anySignal([signal, options.signal]) : signal })
// Remove the successful AbortController so it is not aborted
dialAbortControllers.splice(i, 1)
} finally {
completedDials++
// If we have more or equal dials remaining than tokens, recycle the token, otherwise release it
if (this.addrs.length - completedDials >= tokens.length) {
void tokenHolder.push(token).catch(err => {
log.error(err)
})
} else {
this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0])
}
}
return conn
}))
} finally {
dialAbortControllers.map(c => c.abort()) // success/failure happened, abort everything else
tokens.forEach(token => this.dialer.releaseToken(token)) // release tokens back to the dialer
}
}
}