# Protomux



<Status level="stable" />

`Protomux` multiplexes multiple message-oriented subprotocols over one framed stream. It is typically layered on top of [Secretstream](/p2p/reference/helpers/secretstream) and uses [Compact encoding](/p2p/reference/helpers/compact-encoding) for message schemas. For source and releases, see the [Protomux repository](https://github.com/holepunchto/protomux).

## Install [#install]

```sh
npm i protomux
```

## Quickstart [#quickstart]

```js
import Protomux from 'protomux'
import cenc from 'compact-encoding'

const mux = new Protomux(aFramedEncryptedStream)

const channel = mux.createChannel({
  protocol: 'chat',
  onopen() {
    console.log('chat channel opened')
  }
})

const message = channel.addMessage({
  encoding: cenc.string,
  onmessage(text) {
    console.log(text)
  }
})

channel.open()
message.send('hello')
```

## API Reference [#api-reference]

### Constructor and mux helpers [#constructor-and-mux-helpers]

#### `mux = new Protomux(stream, [options])` [#mux--new-protomuxstream-options]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L332)

Make a new instance. `stream` should be a framed stream, preserving the messages written.

| Parameter | Type                                  | Default | Description                                                                 |
| --------- | ------------------------------------- | ------- | --------------------------------------------------------------------------- |
| `stream`  | `object`                              | —       | —                                                                           |
| `options` | [`ProtomuxOptions`](#protomuxoptions) | `{}`    | Optional configuration for the instance, such as a custom buffer allocator. |

```js
{
  // Called when the muxer wants to allocate a message that is written.
  // Defaults to the stream's own alloc method when it has one, otherwise Buffer.allocUnsafe.
  alloc (size) {}
}
```

#### `mux = Protomux.from(stream | muxer, [options])` [#mux--protomuxfromstream--muxer-options]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L368)

Helper to accept either an existing muxer instance or a stream (which creates a new one).

| Parameter | Type                                  | Description                                                  |
| --------- | ------------------------------------- | ------------------------------------------------------------ |
| `stream`  | `object`                              | A framed stream or an existing Protomux instance.            |
| `opts`    | [`ProtomuxOptions`](#protomuxoptions) | Muxer options passed through when a new instance is created. |

* Returns: [`Protomux`](/p2p/reference/helpers/protomux) — The existing or newly created Protomux instance.

```js
const mux = Protomux.from(someStream)
```

#### `Protomux.isProtomux(mux)` [#protomuxisprotomuxmux]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L374)

Returns `true` when `value` looks like a Protomux instance.

| Parameter | Type     | Description    |
| --------- | -------- | -------------- |
| `mux`     | `object` | Value to test. |

* Returns: `boolean` — `true` when `mux` is a Protomux instance.

```js
Protomux.isProtomux(mux) // true
Protomux.isProtomux({})  // false
```

#### `mux.cork()` [#muxcork]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L388)

Same as `channel.cork` but on the muxer instance.

* Returns: `void`

```js
mux.cork()
channelA.messages[0].send('hello')
channelB.messages[0].send('world')
mux.uncork() // sends both as one frame
```

#### `mux.uncork()` [#muxuncork]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L395)

Same as `channel.uncork` but on the muxer instance.

* Returns: `void`

```js
mux.cork()
msg.send('hello')
mux.uncork()
```

#### `mux.isIdle()` [#muxisidle]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L384)

Convenience method that returns true when no channels are open and no closed channel is still awaiting the remote's acknowledgement.

* Returns: `boolean` — `true` when the muxer has no open channels and none awaiting a remote close acknowledgement.

```js
if (mux.isIdle()) console.log('no open channels')
```

#### `for (const channel of muxer) { ... }` [#for-const-channel-of-muxer---]

The muxer instance is iterable, so you can iterate over all the channels.

### Pairing and channel discovery [#pairing-and-channel-discovery]

#### `mux.pair({ protocol, id }, callback)` [#muxpair-protocol-id--callback]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L410)

Register a callback to be called everytime a new channel is requested.

| Parameter | Type                        | Description                                                                              |
| --------- | --------------------------- | ---------------------------------------------------------------------------------------- |
| `options` | [`ChannelKey`](#channelkey) | Protocol name and optional binary id to match.                                           |
| `notify`  | `Function`                  | Async callback called with the channel's binary id when a matching remote channel opens. |

* Returns: `void`

```js
mux.pair({ protocol: 'my-protocol' }, async (id) => {
  const channel = mux.createChannel({ protocol: 'my-protocol' })
  channel.open()
})
```

#### `mux.unpair({ protocol, id })` [#muxunpair-protocol-id-]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L414)

Unregisters the pair callback.

| Parameter | Type                        | Description                                         |
| --------- | --------------------------- | --------------------------------------------------- |
| `options` | [`ChannelKey`](#channelkey) | Protocol name and optional binary id to deregister. |

* Returns: `void`

```js
mux.unpair({ protocol: 'my-protocol' })
```

#### `mux.watch({ protocol, id }, callback)` [#muxwatch-protocol-id--callback]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L420)

Register a callback to be called every time a channel for this `protocol` (and `id`, if you give one) becomes fully open. Added in 3.13.0.

Unlike [`mux.pair()`](#muxpair-protocol-id--callback), a watcher only observes. It does not accept or create a channel, and any number of watchers can share one `protocol` and `id`, so code that does not own a protocol can still see its channels open.

| Parameter  | Type                        | Description                                                                                                                        |
| ---------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `options`  | [`ChannelKey`](#channelkey) | Protocol name and optional binary id. Leave `id` out or set it to `null` to watch every channel for the protocol, whatever its id. |
| `callback` | `Function`                  | Called with the channel once it is fully open. It runs on both sides of the connection, and `channel.opened` is `true` inside it.  |

* Returns: `void`

A watcher registered for an exact `id` runs before the watchers registered without one, and a channel opened without an id runs the watchers registered without one once. A watcher only sees channels that open after it is registered, so a channel that is already open is not reported.

A watcher cannot break the channel. If `callback` throws, or returns a promise that rejects, the stream emits a `warning` event with the error, not an `error`. The channel stays open and the other watchers still run.

```js
mux.watch({ protocol: 'my-protocol' }, (channel) => {
  console.log('opened', channel.protocol)
})
```

#### `mux.unwatch({ protocol, id }, callback)` [#muxunwatch-protocol-id--callback]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L432)

Remove `callback` from the watchers for this `protocol` and `id`. A callback that was registered twice is removed once per call, and the call does nothing when the callback is not registered. Added in 3.13.0.

| Parameter  | Type                        | Description                                                            |
| ---------- | --------------------------- | ---------------------------------------------------------------------- |
| `options`  | [`ChannelKey`](#channelkey) | Protocol name and optional binary id the callback was registered with. |
| `callback` | `Function`                  | The function that was passed to `mux.watch()`.                         |

* Returns: `void`

#### `mux.unwatchAll({ protocol, id })` [#muxunwatchall-protocol-id-]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L444)

Remove every watcher registered for this `protocol` and `id`. Watchers registered for the same protocol without an id are a separate set, so `mux.unwatchAll({ protocol, id })` leaves them in place. Added in 3.13.0.

| Parameter | Type                        | Description                           |
| --------- | --------------------------- | ------------------------------------- |
| `options` | [`ChannelKey`](#channelkey) | Protocol name and optional binary id. |

* Returns: `void`

#### `mux.opened({ protocol, id })` [#muxopened-protocol-id-]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L447)

Boolean that indicates if the channel is opened.

| Parameter | Type                        | Description                                    |
| --------- | --------------------------- | ---------------------------------------------- |
| `options` | [`ChannelKey`](#channelkey) | Protocol name and optional binary id to check. |

* Returns: `boolean` — `true` when one or more matching channels are open.

```js
if (mux.opened({ protocol: 'my-protocol' })) {
  console.log('already open')
}
```

#### `mux.getLastChannel(options)` [#muxgetlastchanneloptions]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L403)

Returns the channel that most recently opened. Returns `null` once that channel closes, even if older channels with the same protocol and id are still open.

| Parameter | Type                        | Description                           |
| --------- | --------------------------- | ------------------------------------- |
| `options` | [`ChannelKey`](#channelkey) | Protocol name and optional binary id. |

* Returns: [`Channel\|null`](/p2p/reference/helpers/protomux) — The most recently opened matching channel, or `null`.

```js
const ch = mux.getLastChannel({ protocol: 'my-protocol' })
```

### Creating channels [#creating-channels]

#### `mux.createChannel(opts)` [#muxcreatechannelopts]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L453)

Add a new protocol channel.

| Parameter | Type                                            | Description               |
| --------- | ----------------------------------------------- | ------------------------- |
| `opts`    | [`CreateChannelOptions`](#createchanneloptions) | Channel creation options. |

* Returns: [`Channel\|null`](/p2p/reference/helpers/protomux) — The new channel, or `null` if it cannot be opened.

### Mux properties [#mux-properties]

#### `mux.stream` [#muxstream]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L336)

The framed transport stream backing this mux.

* Returns: `object`

#### `mux.drained` [#muxdrained]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L338)

The current writable backpressure state from the underlying stream.

* Returns: `boolean`

### Channel lifecycle [#channel-lifecycle]

#### `channel.open([handshake])` [#channelopenhandshake]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L87)

Open the channel.

| Parameter   | Type | Description                                                                               |
| ----------- | ---- | ----------------------------------------------------------------------------------------- |
| `handshake` | `*`  | Optional handshake value encoded with the handshake encoding provided to `createChannel`. |

* Returns: `void`

```js
const channel = mux.createChannel({ protocol: 'my-protocol' })
channel.open()
```

#### `channel.fullyOpened()` [#channelfullyopened]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L66)

Resolves to `true` when the channel fully opens or `false` if it closes first.

* Returns: [`Promise<boolean>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)

```js
const opened = await channel.fullyOpened()
if (opened) console.log('channel is open')
```

#### `channel.fullyClosed()` [#channelfullyclosed]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L77)

Resolves once the channel has been destroyed and all pending async handlers have finished.

* Returns: [`Promise<void>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)

```js
await channel.fullyClosed()
console.log('channel destroyed')
```

#### `channel.close()` [#channelclose]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L243)

Closes the protocol channel.

* Returns: `void`

```js
channel.close()
```

#### `channel.cork()` [#channelcork]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L235)

Corking the protocol channel, makes it buffer messages and send them all in a batch when it uncorks.

* Returns: `void`

```js
channel.cork()
one.send('hello')
two.send(true)
channel.uncork() // sends both as a single batch
```

#### `channel.uncork()` [#channeluncork]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L239)

Uncork and send the batch.

* Returns: `void`

```js
channel.cork()
one.send('hello')
channel.uncork()
```

### Registering and sending messages [#registering-and-sending-messages]

#### `channel.addMessage(opts)` [#channeladdmessageopts]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L260)

Add/register a message type for a certain encoding. Options include:

| Parameter | Type                                      | Description                                               |
| --------- | ----------------------------------------- | --------------------------------------------------------- |
| `opts`    | [`AddMessageOptions`](#addmessageoptions) | Message options including encoding and onmessage handler. |

* Returns: `object` — Message object with a `send(data)` method for sending encoded messages.

#### `m.send(data)` [#msenddata]

Send a message.

#### `m.encoding` [#mencoding]

The encoding for this message.

#### `m.onmessage` [#monmessage]

Function that is called when a message arrives.

### Channel properties [#channel-properties]

#### `channel.userData` [#channeluserdata]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L26)

* Returns: The arbitrary metadata value passed to `mux.createChannel(...)`.

#### `channel.protocol` [#channelprotocol]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L27)

* Returns: The channel protocol name.

#### `channel.aliases` [#channelaliases]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L28)

* Returns: The list of alternate protocol names registered for this channel family.

#### `channel.id` [#channelid]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L29)

* Returns: The optional binary identifier distinguishing this channel family.

#### `channel.handshake` [#channelhandshake]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L30)

* Returns: The decoded handshake payload after the channel fully opens, or `null` when no handshake encoder was configured.

#### `channel.messages` [#channelmessages]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L31)

* Returns: The array of registered message descriptors in type order.

#### `channel.opened` [#channelopened]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L33)

#### `channel.closed` [#channelclosed]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L34)

* Returns: `true` after either side closes the channel.

#### `channel.destroyed` [#channeldestroyed]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L35)

* Returns: `true` after the close lifecycle and pending handlers have fully drained.

#### `channel.drained` [#channeldrained]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L62)

`true` when the underlying stream's write buffer is empty.

* Returns: `boolean`

### Lifecycle callbacks [#lifecycle-callbacks]

#### `channel.onopen` [#channelonopen]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L37)

* Returns: Called when the channel opens. `handshake` is the decoded open payload or `null`, and `channel` is the channel instance.

#### `channel.onclose` [#channelonclose]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L38)

* Returns: Called when the channel closes. `isRemote` is `true` when the remote side initiated the close, and `channel` is the channel instance.

#### `channel.ondestroy` [#channelondestroy]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L39)

* Returns: Called when the channel is destroyed. `channel` is the destroyed channel instance.

#### `channel.ondrain` [#channelondrain]

[src](https://github.com/holepunchto/protomux/blob/v3.13.0/index.js#L40)

* Returns: Called when the channel's underlying mux becomes writable again. `channel` is that channel instance.

<Callout type="info">
  If an async `onmessage`/`onopen`/`onclose`/`ondrain`/`ondestroy` handler returns a promise that rejects after its channel has already closed, the underlying stream emits a `warning` event with the error instead of destroying the stream (added in v3.12.0). While the channel is still open, a rejection still destroys the stream as before, and a handler that throws synchronously is not caught here at all — listen for `warning` on the stream if you rely on catching post-close handler errors.
</Callout>

## Types [#types]

### ProtomuxOptions [#protomuxoptions]

Options for allocating buffers in a Protomux instance.

| Property | Type       | Default | Description                                                                                                                                                     |
| -------- | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alloc`  | `Function` | —       | Custom allocator; called with `(size)` and must return a `Buffer`. Defaults to the stream's own `alloc` method when it has one, otherwise `Buffer.allocUnsafe`. |

### CreateChannelOptions [#createchanneloptions]

Options for creating a protocol channel.

| Property    | Type                                                                                                      | Default | Description                                                                              |
| ----------- | --------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `protocol`  | `string`                                                                                                  | —       | Protocol name used to match channels between peers.                                      |
| `id`        | [`Buffer\|null`](https://nodejs.org/api/buffer.html#class-buffer)                                         | `null`  | Optional binary identifier to distinguish multiple channels with the same protocol name. |
| `handshake` | `*`                                                                                                       | `null`  | Compact-encoding codec for encoding/decoding the handshake value exchanged on open.      |
| `messages`  | [`Array<AddMessageOptions>`](#addmessageoptions)                                                          | `[]`    | Array of message descriptors registered when the channel is created.                     |
| `unique`    | `boolean`                                                                                                 | `true`  | When `true`, returns `null` if a channel with this protocol+id is already open.          |
| `aliases`   | [`Array<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | `[]`    | Alternative protocol names that also match this channel.                                 |
| `userData`  | `*`                                                                                                       | `null`  | Arbitrary value stored as `channel.userData`; not transmitted.                           |
| `onopen`    | `Function`                                                                                                | —       | Called with `(handshake, channel)` when the remote side opens this protocol.             |
| `onclose`   | `Function`                                                                                                | —       | Called with `(isRemote, channel)` when either side closes the channel.                   |
| `ondestroy` | `Function`                                                                                                | —       | Called with `(channel)` after `onclose` resolves and all pending promises settle.        |
| `ondrain`   | `Function`                                                                                                | —       | Called with `(channel)` when the underlying stream drains.                               |

### AddMessageOptions [#addmessageoptions]

Options for registering a message type on a channel.

| Property    | Type       | Default | Description                                                                    |
| ----------- | ---------- | ------- | ------------------------------------------------------------------------------ |
| `encoding`  | `object`   | —       | A compact-encoding codec. Defaults to raw binary (`c.raw`).                    |
| `autoBatch` | `boolean`  | `true`  | When `true`, batch replies are collected before the next tick.                 |
| `onmessage` | `Function` | —       | Called with `(message, channel)` when the remote sends a message of this type. |

### ChannelKey [#channelkey]

Protocol + id selector used by pair, unpair, watch, unwatch, unwatchAll, opened, and getLastChannel.

| Property   | Type                                                              | Default | Description                                                                                                                                                                                                                                                   |
| ---------- | ----------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol` | `string`                                                          | —       | Protocol name to match.                                                                                                                                                                                                                                       |
| `id`       | [`Buffer\|null`](https://nodejs.org/api/buffer.html#class-buffer) | `null`  | Optional binary id, combined with `protocol` into an exact key. `opened` and `getLastChannel` only match channels created with the same id; a protocol-only `pair` registration matches channels with any id, and so does a `watch` registered without an id. |

## See also [#see-also]

* [Secretstream](/p2p/reference/helpers/secretstream)—the encrypted framed stream most commonly used underneath Protomux.
* [Compact encoding](/p2p/reference/helpers/compact-encoding)—the schema toolkit Protomux uses for handshakes and message payloads.
* [Hyperswarm](/p2p/reference/building-blocks/hyperswarm)—common peer-transport entry point before you layer on Secretstream and Protomux.
* [Upstream Protomux repository](https://github.com/holepunchto/protomux)—source, releases, and implementation details.
