Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

feat: custom address filter #116

Merged
merged 3 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,50 @@
> npm i libp2p-websockets
```

### Example
### Constructor properties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is a good time to just replace this example with how to use it in a libp2p configuration with the passed options. That's likely much more useful than the existing examples as most people won't use this standalone.


```js
const WS = require('libp2p-websockets')

const properties = {
upgrader,
filter
}

const ws = new WS(properties)
```

| Name | Type | Description | Default |
|------|------|-------------|---------|
| upgrader | [`Upgrader`](https://github.com/libp2p/interface-transport#upgrader) | connection upgrader object with `upgradeOutbound` and `upgradeInbound` | **REQUIRED** |
| filter | `(multiaddrs: Array<Multiaddr>) => Array<Multiaddr>` | override transport addresses filter | **Browser:** DNS+WSS multiaddrs / **Node.js:** DNS+{WS, WSS} multiaddrs |

## Libp2p Usage Example

```js
const Libp2p = require('libp2p')
const Websockets = require('libp2p-websockets')
const filters = require('libp2p-websockets/src/filters')

const transportKey = Websockets.prototype[Symbol.toStringTag]
const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
transport: [WebRTCStar],
transport: [Websockets],

// ... other mandatory modules
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just add NOISE and mplex in here. There aren't other options right now, so having a config people can just use would be helpful imo.

},
config: {
transport: {
[transportKey]: { // Transport properties -- Libp2p upgrader is automatically added
filter: filters.dnsWsOrWss
}
}
}
})
```

For more information see [libp2p/js-libp2p/doc/CONFIGURATION.md#customizing-transports](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#customizing-transports).

## Base Example
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just delete this whole section, nobody should use it like this.


```js
const WS = require('libp2p-websockets')
Expand Down Expand Up @@ -69,6 +112,26 @@ console.log(`Value: ${values.toString()}`)
await listener.close()
```

## Custom filters Example

You can create your own address filters for this transports, or rely in the filters [provided](./src/filters.js).

```js
const WS = require('libp2p-websockets')
const filters = require('libp2p-websockets/src/filters')

const ws = new WS({ upgrader, filter: filters.all })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think the libp2p transport configuration example will be ideal here, https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#customizing-transports.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the libp2p usage example, but kept the other. They could still be relevant, but the Readme might get too noisy. What's your opinion?

```

The available filters are:

- `filters.all`
- Returns all TCP and DNS based addresses, both with `ws` or `wss`.
- `filters.dnsWss`
- Returns all DNS based addresses with `wss`.
- `filters.dnsWsOrWss`
- Returns all DNS based addresses, both with `ws` or `wss`.

## API

### Transport
Expand Down
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,24 @@
"dependencies": {
"abortable-iterator": "^3.0.0",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^2.0.0",
"it-ws": "^3.0.0",
"libp2p-utils": "^0.2.0",
"mafmt": "^8.0.0",
"multiaddr": "^8.0.0",
"debug": "^4.2.0",
"err-code": "^2.0.3",
"ipfs-utils": "^4.0.1",
"it-ws": "^3.0.2",
"libp2p-utils": "^0.2.1",
"mafmt": "^8.0.1",
"multiaddr": "^8.1.1",
"multiaddr-to-uri": "^6.0.0",
"p-timeout": "^3.2.0"
},
"devDependencies": {
"abort-controller": "^3.0.0",
"aegir": "^25.0.0",
"aegir": "^28.1.0",
"bl": "^4.0.0",
"is-loopback-addr": "^1.0.1",
"it-goodbye": "^2.0.1",
"it-pipe": "^1.0.1",
"libp2p-interfaces": "^0.4.0",
"libp2p-interfaces": "^0.7.1",
"streaming-iterables": "^5.0.2",
"uint8arrays": "^1.1.0"
},
Expand Down
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
exports.CODE_P2P = 421
exports.CODE_CIRCUIT = 290

exports.CODE_TCP = 6
exports.CODE_WS = 477
exports.CODE_WSS = 478

// Time to wait for a connection to close gracefully before destroying it manually
exports.CLOSE_TIMEOUT = 2000
49 changes: 49 additions & 0 deletions src/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const mafmt = require('mafmt')
const {
CODE_CIRCUIT,
CODE_P2P,
CODE_TCP,
CODE_WS,
CODE_WSS
} = require('./constants')

module.exports = {
all: (multiaddrs) => multiaddrs.filter((ma) => {
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
return false
}

const testMa = ma.decapsulateCode(CODE_P2P)

return mafmt.WebSockets.matches(testMa) ||
mafmt.WebSocketsSecure.matches(testMa)
}),
dnsWss: (multiaddrs) => multiaddrs.filter((ma) => {
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
return false
}

const testMa = ma.decapsulateCode(CODE_P2P)

return mafmt.WebSocketsSecure.matches(testMa) &&
mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS))
}),
dnsWsOrWss: (multiaddrs) => multiaddrs.filter((ma) => {
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
return false
}

const testMa = ma.decapsulateCode(CODE_P2P)

// WS
if (mafmt.WebSockets.matches(testMa)) {
return mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WS))
}

// WSS
return mafmt.WebSocketsSecure.matches(testMa) &&
mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS))
})
}
38 changes: 23 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
'use strict'

const connect = require('it-ws/client')
const mafmt = require('mafmt')
const withIs = require('class-is')
const toUri = require('multiaddr-to-uri')
const { AbortError } = require('abortable-iterator')

const log = require('debug')('libp2p:websockets')
const env = require('ipfs-utils/src/env')

const createListener = require('./listener')
const toConnection = require('./socket-to-conn')
const { CODE_CIRCUIT, CODE_P2P } = require('./constants')
const filters = require('./filters')

/**
* @class WebSockets
*/
class WebSockets {
/**
* @constructor
* @class
* @param {object} options
* @param {Upgrader} options.upgrader
* @param {(multiaddrs: Array<Multiaddr>) => Array<Multiaddr>} options.filter - override transport addresses filter
*/
constructor ({ upgrader }) {
constructor ({ upgrader, filter }) {
if (!upgrader) {
throw new Error('An upgrader must be provided. See https://github.com/libp2p/interface-transport#upgrader.')
}
this._upgrader = upgrader
this._filter = filter
}

/**
* @async
* @param {Multiaddr} ma
* @param {object} [options]
* @param {AbortSignal} [options.signal] Used to abort dial requests
* @param {AbortSignal} [options.signal] - Used to abort dial requests
* @returns {Connection} An upgraded Connection
*/
async dial (ma, options = {}) {
Expand All @@ -51,7 +53,7 @@ class WebSockets {
* @private
* @param {Multiaddr} ma
* @param {object} [options]
* @param {AbortSignal} [options.signal] Used to abort dial requests
* @param {AbortSignal} [options.signal] - Used to abort dial requests
* @returns {Promise<WebSocket>} Resolves a extended duplex iterable on top of a WebSocket
*/
async _connect (ma, options = {}) {
Expand Down Expand Up @@ -97,8 +99,9 @@ class WebSockets {
* Creates a Websockets listener. The provided `handler` function will be called
* anytime a new incoming Connection has been successfully upgraded via
* `upgrader.upgradeInbound`.
*
* @param {object} [options]
* @param {http.Server} [options.server] A pre-created Node.js HTTP/S server.
* @param {http.Server} [options.server] - A pre-created Node.js HTTP/S server.
* @param {function (Connection)} handler
* @returns {Listener} A Websockets listener
*/
Expand All @@ -112,21 +115,26 @@ class WebSockets {
}

/**
* Takes a list of `Multiaddr`s and returns only valid Websockets addresses
* Takes a list of `Multiaddr`s and returns only valid Websockets addresses.
* By default, in a browser environment only DNS+WSS multiaddr is accepted,
* while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
*
* @param {Multiaddr[]} multiaddrs
* @returns {Multiaddr[]} Valid Websockets multiaddrs
*/
filter (multiaddrs) {
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]

return multiaddrs.filter((ma) => {
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
return false
}
if (this._filter) {
return this._filter(multiaddrs)
}

return mafmt.WebSockets.matches(ma.decapsulateCode(CODE_P2P)) ||
mafmt.WebSocketsSecure.matches(ma.decapsulateCode(CODE_P2P))
})
// Browser
if (env.isBrowser || env.isWebWorker) {
return filters.dnsWss(multiaddrs)
}

return filters.all(multiaddrs)
}
}

Expand Down
10 changes: 10 additions & 0 deletions test/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ describe('libp2p-websockets', () => {
expect(results).to.eql([message])
})

it('should filter out no DNS websocket addresses', function () {
const ma1 = multiaddr('/ip4/127.0.0.1/tcp/80/ws')
const ma2 = multiaddr('/ip4/127.0.0.1/tcp/443/wss')
const ma3 = multiaddr('/ip6/::1/tcp/80/ws')
const ma4 = multiaddr('/ip6/::1/tcp/443/wss')

const valid = ws.filter([ma1, ma2, ma3, ma4])
expect(valid.length).to.equal(0)
})

describe('stress', () => {
it('one big write', async () => {
const rawMessage = new Uint8Array(1000000).fill('a')
Expand Down
3 changes: 2 additions & 1 deletion test/compliance.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const tests = require('libp2p-interfaces/src/transport/tests')
const multiaddr = require('multiaddr')
const http = require('http')
const WS = require('../src')
const filters = require('../src/filters')

describe('interface-transport compliance', () => {
tests({
async setup ({ upgrader }) { // eslint-disable-line require-await
const ws = new WS({ upgrader })
const ws = new WS({ upgrader, filter: filters.all })
const addrs = [
multiaddr('/ip4/127.0.0.1/tcp/9091/ws'),
multiaddr('/ip4/127.0.0.1/tcp/9092/ws'),
Expand Down
Loading