@web3-storage/multistream-select
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

js-multistream-select

JavaScript implementation of multistream-select with support for lazy select.

Table of Contents

Background

What is multistream-select?

TLDR; multistream-select is protocol multiplexing per connection/stream. Full spec here

Select a protocol flow

The caller will send "interactive" messages, expecting for some acknowledgement from the callee, which will "select" the handler for the desired and supported protocol:

< /multistream-select/0.3.0  # i speak multistream-select/0.3.0
> /multistream-select/0.3.0  # ok, let's speak multistream-select/0.3.0
> /ipfs-dht/0.2.3            # i want to speak ipfs-dht/0.2.3
< na                         # ipfs-dht/0.2.3 is not available
> /ipfs-dht/0.1.9            # What about ipfs-dht/0.1.9 ?
< /ipfs-dht/0.1.9            # ok let's speak ipfs-dht/0.1.9 -- in a sense acts as an ACK
> <dht-message>
> <dht-message>
> <dht-message>

This mode also packs a ls option, so that the callee can list the protocols it currently supports

Install

npm i @libp2p/multistream-select

Usage

import { Dialer, Listener } from '@libp2p/multistream-select'
// You can now use
// Dialer - actively select a protocol with a remote
// Listener - handle a protocol with a remote

Dialer

import { pipe } from 'it-pipe'
import { Dialer } from '@libp2p/multistream-select'
import { Mplex } from '@libp2p/mplex'

const muxer = new Mplex()
const muxedStream = muxer.newStream()

const mss = new Dialer(muxedStream)

// mss.select(protocol(s))
// Select from one of the passed protocols (in priority order)
// Returns selected stream and protocol
const { stream: dhtStream, protocol } = await mss.select([
  // This might just be different versions of DHT, but could be different impls
  '/ipfs-dht/2.0.0', // Most of the time this will probably just be one item.
  '/ipfs-dht/1.0.0'
])

// Typically this stream will be passed back to the caller of libp2p.dialProtocol
//
// ...it might then do something like this:
// try {
//   await pipe(
//     [uint8ArrayFromString('Some DHT data')]
//     dhtStream,
//     async source => {
//       for await (const chunk of source)
//         // DHT response data
//     }
//   )
// } catch (err) {
//   // Error in stream
// }

Listener

import { pipe } from 'it-pipe'
import { Listener } from '@libp2p/multistream-select'
import { Mplex } from '@libp2p/mplex'

const muxer = new Mplex({
  async onStream (muxedStream) {
    const mss = new Listener(muxedStream)

    // mss.handle(handledProtocols)
    // Returns selected stream and protocol
    const { stream, protocol } = await mss.handle([
      '/ipfs-dht/1.0.0',
      '/ipfs-bitswap/1.0.0'
    ])

    // Typically here we'd call the handler function that was registered in
    // libp2p for the given protocol:
    // e.g. handlers[protocol].handler(stream)
    //
    // If protocol was /ipfs-dht/1.0.0 it might do something like this:
    // try {
    //   await pipe(
    //     dhtStream,
    //     source => (async function * () {
    //       for await (const chunk of source)
    //         // Incoming DHT data -> process and yield to respond
    //     })(),
    //     dhtStream
    //   )
    // } catch (err) {
    //   // Error in stream
    // }
  }
})

API

new Dialer(duplex)

Create a new multistream select "dialer" instance which can be used to negotiate a protocol to use, list all available protocols the remote supports, or do both.

Parameters

Returns

A new multistream select dialer instance.

Examples

const dialer = new MSS.Dialer(duplex)

dialer.select(protocols, [options])

Negotiate a protocol to use from a list of protocols.

Parameters

  • protocols (String[]/String) - A list of protocols (or single protocol) to negotiate with. Protocols are attempted in order until a match is made.
  • options ({ signal: AbortSignal, lazy: boolean }) - an options object containing an AbortSignal and lazy flag. If lazy is set to try, and only 1 protocol is available for selection then the dialer will optimistically assume protocol selection and start writing data without waiting for the protocol selection response.

Returns

Promise<{ stream<Object>, protocol<String> }> - A stream for the selected protocol and the protocol that was selected from the list of protocols provided to select.

Note that after a protocol is selected dialer can no longer be used.

Examples

const { stream, protocol } = await dialer.select([
  // This might just be different versions of DHT, but could be different impls
  '/ipfs-dht/2.0.0', // Most of the time this will probably just be one item.
  '/ipfs-dht/1.0.0'
])
// Now talk `protocol` on `stream`

dialer.ls([options])

List protocols that the remote supports.

Parameters

  • options ({ signal: AbortSignal }) - an options object containing an AbortSignal

Returns

String[] - A list of all the protocols the remote supports.

Examples

const protocols = await dialer.ls()
const wantedProto = '/ipfs-dht/2.0.0'

if (!protocols.includes(wantedProto)) {
  throw new Error('remote does not support ' + wantedProto)
}

// Now use dialer.select to use wantedProto, safe in the knowledge it is supported

new Listener(duplex)

Construct a new multistream select "listener" instance which can be used to handle multistream protocol selections for particular protocols.

Parameters

Returns

A new multistream select listener instance.

Examples

const listener = new MSS.Listener(duplex)

listener.handle(protocols, [options])

Handle multistream protocol selections for the given list of protocols.

Parameters

  • protocols (String[]/String) - A list of protocols (or single protocol) that this listener is able to speak.
  • options ({ signal: AbortSignal }) - an options object containing an AbortSignal

Returns

Promise<{ stream<Object>, protocol<String> }> - A stream for the selected protocol and the protocol that was selected from the list of protocols provided to select.

Note that after a protocol is handled listener can no longer be used.

Examples

const { stream, protocol } = await listener.handle([
  '/ipfs-dht/1.0.0',
  '/ipfs-bitswap/1.0.0'
])
// Remote wants to speak `protocol`

License

Licensed under either of

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Package Sidebar

Install

npm i @web3-storage/multistream-select

Weekly Downloads

1

Version

1.0.1

License

Apache-2.0 OR MIT

Unpacked Size

53.2 kB

Total Files

33

Last publish

Collaborators

  • it-dag-house
  • gozala
  • olizilla
  • vascosantos
  • alanshaw