diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2969bd0..83edf7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: - js-libp2p-example-pnet - js-libp2p-example-protocol-and-stream-muxing - js-libp2p-example-pubsub + - js-libp2p-example-transports - js-libp2p-example-webrtc-private-to-private defaults: run: @@ -90,6 +91,7 @@ jobs: - js-libp2p-example-pnet - js-libp2p-example-protocol-and-stream-muxing - js-libp2p-example-pubsub + - js-libp2p-example-transports - js-libp2p-example-webrtc-private-to-private steps: - uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be diff --git a/examples/js-libp2p-example-transports/.github/pull_request_template.md b/examples/js-libp2p-example-transports/.github/pull_request_template.md new file mode 100644 index 0000000..accc1b8 --- /dev/null +++ b/examples/js-libp2p-example-transports/.github/pull_request_template.md @@ -0,0 +1,17 @@ +# ⚠️ IMPORTANT ⚠️ + +# Please do not create a Pull Request for this repository + +The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. + +Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) +2. Create your Feature Branch (`git checkout -b feature/amazing-example`) +3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) +4. Push to the Branch (`git push origin feature/amazing-example`) +5. Open a Pull Request diff --git a/examples/js-libp2p-example-transports/.github/workflows/sync.yml b/examples/js-libp2p-example-transports/.github/workflows/sync.yml new file mode 100644 index 0000000..78f6c8d --- /dev/null +++ b/examples/js-libp2p-example-transports/.github/workflows/sync.yml @@ -0,0 +1,19 @@ +name: pull + +on: + workflow_dispatch + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Pull from another repository + uses: ipfs-examples/actions-pull-directory-from-repo@main + with: + source-repo: libp2p/js-libp2p-examples + source-folder-path: examples/${{ github.event.repository.name }} + source-branch: main + target-branch: main + git-username: github-actions + git-email: github-actions@github.com diff --git a/examples/js-libp2p-example-transports/1.js b/examples/js-libp2p-example-transports/1.js new file mode 100644 index 0000000..e32e042 --- /dev/null +++ b/examples/js-libp2p-example-transports/1.js @@ -0,0 +1,31 @@ +/* eslint-disable no-console */ + +import { noise } from '@chainsafe/libp2p-noise' +import { tcp } from '@libp2p/tcp' +import { createLibp2p } from 'libp2p' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + // To signal the addresses we want to be available, we use + // the multiaddr format, a self describable address + listen: [ + '/ip4/0.0.0.0/tcp/0' + ] + }, + transports: [ + tcp() + ], + connectionEncrypters: [ + noise() + ] + }) + + return node +} + +const node = await createNode() + +console.log('node has started') +console.log('listening on:') +node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) diff --git a/examples/js-libp2p-example-transports/2.js b/examples/js-libp2p-example-transports/2.js new file mode 100644 index 0000000..9acf08b --- /dev/null +++ b/examples/js-libp2p-example-transports/2.js @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ + +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { tcp } from '@libp2p/tcp' +import { pipe } from 'it-pipe' +import toBuffer from 'it-to-buffer' +import { createLibp2p } from 'libp2p' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + // To signal the addresses we want to be available, we use + // the multiaddr format, a self describable address + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [tcp()], + connectionEncrypters: [noise()], + streamMuxers: [yamux()] + }) + + return node +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) +} + +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) + +printAddrs(node1, '1') +printAddrs(node2, '2') + +node2.handle('/print', async ({ stream }) => { + const result = await pipe( + stream, + async function * (source) { + for await (const list of source) { + yield list.subarray() + } + }, + toBuffer + ) + console.log(uint8ArrayToString(result)) +}) + +await node1.peerStore.patch(node2.peerId, { + multiaddrs: node2.getMultiaddrs() +}) +const stream = await node1.dialProtocol(node2.peerId, '/print') + +await pipe( + ['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)), + stream +) diff --git a/examples/js-libp2p-example-transports/3.js b/examples/js-libp2p-example-transports/3.js new file mode 100644 index 0000000..7e87d84 --- /dev/null +++ b/examples/js-libp2p-example-transports/3.js @@ -0,0 +1,88 @@ +/* eslint-disable no-console */ + +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { pipe } from 'it-pipe' +import { createLibp2p } from 'libp2p' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' + +const createNode = async (transports, addresses = []) => { + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + const node = await createLibp2p({ + addresses: { + listen: addresses + }, + transports, + connectionEncrypters: [noise()], + streamMuxers: [yamux()] + }) + + return node +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) +} + +function print ({ stream }) { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(uint8ArrayToString(msg.subarray())) + } + } + ) +} + +const [node1, node2, node3] = await Promise.all([ + createNode([tcp()], '/ip4/0.0.0.0/tcp/0'), + createNode([tcp(), webSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), + createNode([webSockets()], '/ip4/127.0.0.1/tcp/20000/ws') +]) + +printAddrs(node1, '1') +printAddrs(node2, '2') +printAddrs(node3, '3') + +node1.handle('/print', print) +node2.handle('/print', print) +node3.handle('/print', print) + +await node1.peerStore.patch(node2.peerId, { + multiaddrs: node2.getMultiaddrs() +}) +await node2.peerStore.patch(node3.peerId, { + multiaddrs: node3.getMultiaddrs() +}) +await node3.peerStore.patch(node1.peerId, { + multiaddrs: node1.getMultiaddrs() +}) + +// node 1 (TCP) dials to node 2 (TCP+WebSockets) +const stream = await node1.dialProtocol(node2.peerId, '/print') +await pipe( + [uint8ArrayFromString('node 1 dialed to node 2 successfully')], + stream +) + +// node 2 (TCP+WebSockets) dials to node 3 (WebSockets) +const stream2 = await node2.dialProtocol(node3.peerId, '/print') +await pipe( + [uint8ArrayFromString('node 2 dialed to node 3 successfully')], + stream2 +) + +// node 3 (listening WebSockets) can dial node 1 (TCP) +try { + await node3.dialProtocol(node1.peerId, '/print') +} catch (err) { + console.log('node 3 failed to dial to node 1 with:', err.message) +} diff --git a/examples/js-libp2p-example-transports/4.js b/examples/js-libp2p-example-transports/4.js new file mode 100644 index 0000000..8354fed --- /dev/null +++ b/examples/js-libp2p-example-transports/4.js @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ + +import fs from 'fs' +import https from 'https' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { pipe } from 'it-pipe' +import { createLibp2p } from 'libp2p' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' + +const httpServer = https.createServer({ + cert: fs.readFileSync('./test_certs/cert.pem'), + key: fs.readFileSync('./test_certs/key.pem') +}) + +const createNode = async (addresses = []) => { + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + const node = await createLibp2p({ + addresses: { + listen: addresses + }, + transports: [ + tcp(), + webSockets({ + server: httpServer, + websocket: { + rejectUnauthorized: false + } + }) + ], + connectionEncrypters: [noise()], + streamMuxers: [yamux()] + }) + + return node +} + +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) +} + +function print ({ stream }) { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(uint8ArrayToString(msg.subarray())) + } + } + ) +} + +const [node1, node2] = await Promise.all([ + createNode('/ip4/127.0.0.1/tcp/10000/wss'), + createNode([]) +]) + +printAddrs(node1, '1') +printAddrs(node2, '2') + +node1.handle('/print', print) +node2.handle('/print', print) + +const targetAddr = node1.getMultiaddrs()[0] + +// node 2 (Secure WebSockets) dials to node 1 (Secure Websockets) +const stream = await node2.dialProtocol(targetAddr, '/print') +await pipe( + [uint8ArrayFromString('node 2 dialed to node 1 successfully')], + stream +) diff --git a/examples/js-libp2p-example-transports/LICENSE b/examples/js-libp2p-example-transports/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/examples/js-libp2p-example-transports/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/examples/js-libp2p-example-transports/LICENSE-APACHE b/examples/js-libp2p-example-transports/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/examples/js-libp2p-example-transports/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/examples/js-libp2p-example-transports/LICENSE-MIT b/examples/js-libp2p-example-transports/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/examples/js-libp2p-example-transports/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/js-libp2p-example-transports/README.md b/examples/js-libp2p-example-transports/README.md new file mode 100644 index 0000000..22a0352 --- /dev/null +++ b/examples/js-libp2p-example-transports/README.md @@ -0,0 +1,326 @@ +# @libp2p/example-transports + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) + +> An example using different types of libp2p transport + +A more complete definition of what is a transport can be found on the [interface-transport] specification. A way to recognize a candidate transport is through the badge: + +![][interface-transport badge] + +## 1. Creating a libp2p node with TCP + +When using libp2p, you need properly configure it, that is, pick your set of modules and create your network stack with the properties you need. In this example, we will create a libp2p node TCP. You can find the complete solution on the file [1.js](./1.js). + +You will need 4 dependencies total, so go ahead and install all of them with: + +```bash +> npm install libp2p @libp2p/tcp @chainsafe/libp2p-noise +``` + +Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`. + +First thing is to create our own libp2p node! Insert: + +```JavaScript +import { createLibp2p } from 'libp2p' +import { tcp } from '@libp2p/tcp' +import { noise } from '@chainsafe/libp2p-noise' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + // To signal the addresses we want to be available, we use + // the multiaddr format, a self describable address + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [ + tcp() + ], + connectionEncrypters: [ + noise() + ] + }) + + return node +} +``` + +Now that we have a function to create our own libp2p node, let's create a node with it. + +```JavaScript +const node = await createNode() + +// At this point the node has started +console.log('node has started') +// And we can print the now listening addresses. +// If you are familiar with TCP, you might have noticed +// that we specified the node to listen in 0.0.0.0 and port +// 0, which means "listen in any network interface and pick +// a port for me +console.log('listening on:') +node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) +``` + +Running this should result in something like: + +```bash +> node 1.js +node has started (true/false): true +listening on: +/ip4/127.0.0.1/tcp/61329/p2p/QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ +/ip4/192.168.2.156/tcp/61329/p2p/QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ +``` + +That `QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ` is the PeerId that was created during the PeerInfo generation. + +## 2. Dialing from one node to another node + +Now that we have our `createNode` function, let's create two nodes and make them dial to each other! You can find the complete solution at [2.js](./2.js). + +For this step, we will need some more dependencies. + +```bash +> npm install it-pipe it-all +``` + +And we also need to import the modules on our .js file: + +```js +import { pipe } from 'it-pipe' +import { yamux } from '@chainsafe/libp2p-yamux' +import all from 'it-all' +``` + +We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`. + +```js +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + // To signal the addresses we want to be available, we use + // the multiaddr format, a self describable address + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [tcp()], + connectionEncrypters: [noise()], + streamMuxers: [yamux(), mplex()] // <--- Add this line + }) + + return node +} +``` + +We will also make things simpler by creating another function to print the multiaddresses to avoid duplicating code. + +```JavaScript +function printAddrs (node, number) { + console.log('node %s is listening on:', number) + node.getMultiaddrs().forEach((ma) => console.log(ma.toString())) +} +``` + +Then add, + +```js +import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import { toString as uint8ArrayToString } from "uint8arrays/to-string"; + +const [node1, node2] = await Promise.all([ + createNode(), + createNode() +]) + +printAddrs(node1, '1') +printAddrs(node2, '2') + +node2.handle('/print', async ({ stream }) => { + const result = await pipe( + stream, + all + ) + console.log(result.map(buf => uint8ArrayToString(buf.subarray())).join("")) +}) + +await node1.peerStore.patch(node2.peerId, { + multiaddrs: node2.getMultiaddrs() +}) +const stream = await node1.dialProtocol(node2.peerId, '/print') + +await pipe( + ['Hello', ' ', 'p2p', ' ', 'world', '!'].map(str => uint8ArrayFromString(str)), + stream +) +``` + +For more information refer to the [docs](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md). + +The result should look like: + +```bash +> node 2.js +node 1 is listening on: +/ip4/127.0.0.1/tcp/62279/p2p/QmeM4wNWv1uci7UJjUXZYfvcy9uqAbw7G9icuxdqy88Mj9 +/ip4/192.168.2.156/tcp/62279/p2p/QmeM4wNWv1uci7UJjUXZYfvcy9uqAbw7G9icuxdqy88Mj9 +node 2 is listening on: +/ip4/127.0.0.1/tcp/62278/p2p/QmWp58xJgzbouNJcyiNNTpZuqQCJU8jf6ixc7TZT9xEZhV +/ip4/192.168.2.156/tcp/62278/p2p/QmWp58xJgzbouNJcyiNNTpZuqQCJU8jf6ixc7TZT9xEZhV +Hello p2p world! +``` + +## 3. Using multiple transports + +Next, we want nodes to have multiple transports available to increase their chances of having a common transport in the network to communicate over. A simple scenario is a node running in the browser only having access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport. For this node to dial to some other node, that other node needs to share a common transport. + +What we are going to do in this step is to create 3 nodes: one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js). + +In this example, we will need to also install `@libp2p/websockets`: + +```bash +> npm install @libp2p/websockets +``` + +We want to create 3 nodes: one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to accept WebSocket connections as well. Moreover, let's upgrade our function to enable us to pick the addresses over which a node will start a listener: + +```JavaScript +// ... + +const createNode = async (transports, addresses = []) => { + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + const node = await createLibp2p({ + addresses: { + listen: addresses + }, + transports: transports, + connectionEncrypters: [noise()], + streamMuxers: [yamux(), mplex()] + }) + + return node +} +``` + +As a rule, a libp2p node will only be capable of using a transport if: a) it has the module for it and b) it was given a multiaddr to listen on. The only exception to this rule is WebSockets in the browser, where a node can dial out, but unfortunately cannot open a socket. + +Let's update our flow to create nodes and see how they behave when dialing to each other: + +```JavaScript +import { webSockets } from '@libp2p/websockets' +import { tcp } from '@libp2p/tcp' + +const [node1, node2, node3] = await Promise.all([ + createNode([tcp()], '/ip4/0.0.0.0/tcp/0'), + createNode([tcp(), webSockets()], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']), + createNode([webSockets()], '/ip4/127.0.0.1/tcp/20000/ws') +]) + +printAddrs(node1, '1') +printAddrs(node2, '2') +printAddrs(node3, '3') + +node1.handle('/print', print) +node2.handle('/print', print) +node3.handle('/print', print) + +await node1.peerStore.patch(node2.peerId, { + multiaddrs: node2.getMultiaddrs() +}) +await node2.peerStore.patch(node3.peerId, { + multiaddrs: node3.getMultiaddrs() +}) +await node3.peerStore.patch(node1.peerId, { + multiaddrs: node1.getMultiaddrs() +}) + +// node 1 (TCP) dials to node 2 (TCP+WebSockets) +const stream = await node1.dialProtocol(node2.peerId, '/print') +await pipe( + ['node 1 dialed to node 2 successfully'].map(str => uint8ArrayFromString(str)), + stream +) + +// node 2 (TCP+WebSockets) dials to node 3 (WebSockets) +const stream2 = await node2.dialProtocol(node3.peerId, '/print') +await pipe( + ['node 2 dialed to node 3 successfully'].map(str => uint8ArrayFromString(str)), + stream2 +) + +// node 3 (WebSockets) attempts to dial to node 1 (TCP) +try { + await node3.dialProtocol(node1.peerId, '/print') +} catch (err) { + console.log('node 3 failed to dial to node 1 with:', err.message) +} +``` + +`print` is a function that prints each piece of data from a stream onto a new line but factored into its own function to save lines: + +```JavaScript +function print ({ stream }) { + pipe( + stream, + async function (source) { + for await (const msg of source) { + console.log(uint8ArrayToString(msg.subarray())) + } + } + ) +} +``` + +If everything was set correctly, you now should see something similar to the following after running the script: + +```Bash +> node 3.js +node 1 is listening on: +/ip4/127.0.0.1/tcp/62620/p2p/QmWpWmcVJkF6EpmAaVDauku8g1uFGuxPsGP35XZp9GYEqs +/ip4/192.168.2.156/tcp/62620/p2p/QmWpWmcVJkF6EpmAaVDauku8g1uFGuxPsGP35XZp9GYEqs +node 2 is listening on: +/ip4/127.0.0.1/tcp/10000/ws/p2p/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +/ip4/127.0.0.1/tcp/62619/p2p/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +/ip4/192.168.2.156/tcp/62619/p2p/QmWAQtWdzWXibgfyc7WRHhhv6MdqVKzXvyfSTnN2aAvixX +node 3 is listening on: +/ip4/127.0.0.1/tcp/20000/ws/p2p/QmVq1PWh3VSDYdFqYMtqp4YQyXcrH27N7968tGdM1VQPj1 +node 1 dialed to node 2 successfully +node 2 dialed to node 3 successfully +node 3 failed to dial to node 1 with: + Error: No transport available for address /ip4/127.0.0.1/tcp/51482 +``` + +As expected, we created 3 nodes: node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport; however, node 3 -> node 1 failed because they didn't share any. + +## 4. How to create a new libp2p transport + +Today there are already several transports available and plenty to come. You can find these at [interface-transport implementations] list. + +Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if it follows the interface defined in the [spec][interface-transport api] it will be able to use it. + +If you decide to implement a transport yourself, please consider adding to the list so that others can use it as well. + +Hope this tutorial was useful. We are always looking to improve it, so contributions are welcome! + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## 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. + +[interface-transport]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport + +[interface-transport badge]: https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/packages/libp2p-interfaces/src/transport/img/badge.png + +[interface-transport implementations]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#modules-that-implement-the-interface + +[interface-transport api]: https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#api diff --git a/examples/js-libp2p-example-transports/package.json b/examples/js-libp2p-example-transports/package.json new file mode 100644 index 0000000..5d4f298 --- /dev/null +++ b/examples/js-libp2p-example-transports/package.json @@ -0,0 +1,32 @@ +{ + "name": "@libp2p/example-transports", + "version": "0.0.0", + "description": "An example using different types of libp2p transport", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p-examples/tree/master/examples/js-libp2p-example-transports#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p-examples.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-examples/issues" + }, + "type": "module", + "scripts": { + "test": "test-node-example test/*" + }, + "dependencies": { + "@chainsafe/libp2p-noise": "^16.0.0", + "@chainsafe/libp2p-yamux": "^7.0.0", + "@libp2p/tcp": "^10.0.0", + "@libp2p/websockets": "^9.0.0", + "it-pipe": "^3.0.1", + "it-to-buffer": "^4.0.2", + "libp2p": "^2.0.0", + "uint8arrays": "^5.1.0" + }, + "devDependencies": { + "test-ipfs-example": "^1.1.0" + }, + "private": true +} diff --git a/examples/js-libp2p-example-transports/test/test-1.js b/examples/js-libp2p-example-transports/test/test-1.js new file mode 100644 index 0000000..603ef19 --- /dev/null +++ b/examples/js-libp2p-example-transports/test/test-1.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('1.js\n') + + await waitForOutput('/p2p/', 'node', [path.join(__dirname, '../1.js')], { + cwd: __dirname + }) +} diff --git a/examples/js-libp2p-example-transports/test/test-2.js b/examples/js-libp2p-example-transports/test/test-2.js new file mode 100644 index 0000000..134945e --- /dev/null +++ b/examples/js-libp2p-example-transports/test/test-2.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('2.js\n') + + await waitForOutput('Hello p2p world!', 'node', [path.join(__dirname, '../2.js')], { + cwd: __dirname + }) +} diff --git a/examples/js-libp2p-example-transports/test/test-3.js b/examples/js-libp2p-example-transports/test/test-3.js new file mode 100644 index 0000000..6c8fabc --- /dev/null +++ b/examples/js-libp2p-example-transports/test/test-3.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('3.js\n') + + await waitForOutput('node 3 failed to dial to node 1 with:', 'node', [path.join(__dirname, '../3.js')], { + cwd: __dirname + }) +} diff --git a/examples/js-libp2p-example-transports/test/test-4.js b/examples/js-libp2p-example-transports/test/test-4.js new file mode 100644 index 0000000..03d03fe --- /dev/null +++ b/examples/js-libp2p-example-transports/test/test-4.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('4.js\n') + + await waitForOutput('node 2 dialed to node 1 successfully', 'node', [path.join(__dirname, '../4.js')], { + cwd: path.join(__dirname, '../') + }) +} diff --git a/examples/js-libp2p-example-transports/test/test.js b/examples/js-libp2p-example-transports/test/test.js new file mode 100644 index 0000000..bc53a61 --- /dev/null +++ b/examples/js-libp2p-example-transports/test/test.js @@ -0,0 +1,9 @@ +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' +import { test as test3 } from './test-3.js' +import { test as test4 } from './test-4.js' + +await test1() +await test2() +await test3() +await test4() diff --git a/examples/js-libp2p-example-transports/test_certs/cert.pem b/examples/js-libp2p-example-transports/test_certs/cert.pem new file mode 100644 index 0000000..0574192 --- /dev/null +++ b/examples/js-libp2p-example-transports/test_certs/cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlzCCA3+gAwIBAgIUMYedwb9L/BtvZ7Lhu71iSKrXsa4wDQYJKoZIhvcNAQEL +BQAwajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMREwDwYDVQQHDAhTb21lQ2l0 +eTESMBAGA1UECgwJTXlDb21wYW55MRMwEQYDVQQLDApNeURpdmlzaW9uMRIwEAYD +VQQDDAkxMjcuMC4wLjEwHhcNMjEwNDI4MDIzMjA5WhcNMjIwNDI4MDIzMjA5WjBq +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVkExETAPBgNVBAcMCFNvbWVDaXR5MRIw +EAYDVQQKDAlNeUNvbXBhbnkxEzARBgNVBAsMCk15RGl2aXNpb24xEjAQBgNVBAMM +CTEyNy4wLjAuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANNhXBu0 +GH1Kzl9iaQxCxEnyyAShS5FYScdKxqpYsgJT4poLWLQBZQEFLEqbdillIlTZqMss +jWqkFL2xmjqdcnOKFEZUarntVE2hxFYYQex2Fi8MYwFj+Pvt74d02xPyfzFNFgyX +a1EakoGBwClaf3I7jW7raPudjcf4HnwQ7r/NwiO8FqHFZgLcTnwI8bk+cxDoDAqu +mhqMB5nnerqvKEyR9Fb2PoL+8PwOPJOOKTDVwLMeMJu2WLR8AU2FzOj5SVI2qsu9 +Ps5azysD8KQAMcw4y9s6do36SaMQS85fbvXBV7XBqMD34HPBUbFiCoFoaCzK9Zfb +pCXyVJMUNmw5hyq9nbjUt4Kvr/58bU2gjUKSdPf6KhBxFnDZwl+2qqPdVIb/qtwz +HExtJWq3upklXNOg3HoR6vcr1O9ReJHrzLRMEb51WP1aN/qJ2/lRskcZ4A806qwr +W67BvnOg6s3ZtxHN9v3bsyfsvC66w8PEfCnCVxugC7cUW0gtW54AU75T3ukg7X+m +vECr/+qIzNEBIxxCPgefCG/JAdJhQ5SCvoARAVPStUIWDmigDeOt7go5nKbdVIJ4 +7bbBFUhHT2mTHu30fHhRqSDcHzwE7Zz6YJIJmKq29UmzUazFnKlLU67MjLJwiDPm +fC3GyOdAWkkZE5hjtkiy+3yWoEHhaJYRI1u3AgMBAAGjNTAzMAsGA1UdDwQEAwIE +MDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3 +DQEBCwUAA4ICAQCx/ynu4iCQAK8VId/QQe7GqgOpFgx+6Mce9GQC6ZVEjAPgapsS +Pl+l6+11cFjHKv0+Z/iN2JgkFmNXfwJcfYI0tHbMK+0U9hgKb1eFgiIwCqb4cPOz +wMwusZ95BjIbtcEbL/+pMUpNhmjPz1fOILJZtDVq++lqJCv7t8+SoAmMVYtlcLNg +muuV/UYR3uqvnAJmjgJVWs4otDGrxCYJE48M+9L2Gm05Htpi9WL1bZaQ+fJ85m85 +daedLc6R1/ZRTIH6i73sD4rYs0bx1fCJvkbcgXtKMHEkiHuG/MzR7Pa4cJAVKCx9 +lRTgrO7Gkllt2+jp4qg0YhdNq89e0DNA5cyB9H4udRgHQOcrlVRiX9OD/Kz+F5m/ +fQwMdbnqdg3ar5DSa8Q5g3bdLbNSCcI9sjCLTkNxUC/XTWGdG03RCVIt1qvBvZHk +JaG6xGpbRZ5CN0T9eindd38JBrkPAPfgl6qhwvcqh6uVFYua+7KmF9K+mKarlmMw +6RWaw2j4sMgUyRIS6fR9vDc20SrtoNvKQM1U6+0VYs1nizfkmsqqqRODmERKbKwc +ahKJFubXfr8gz+PipAKFZbxr2EPAyoiNkx+0eM6Eedo55oP2BoGHEfXEoAonyMFM +F/xTbpFtdRYE2hwsZCk86fpbcPTmdCY8txeZ7+4Bme2d9XXsTAxF64usqQ== +-----END CERTIFICATE----- diff --git a/examples/js-libp2p-example-transports/test_certs/key.pem b/examples/js-libp2p-example-transports/test_certs/key.pem new file mode 100644 index 0000000..dfee301 --- /dev/null +++ b/examples/js-libp2p-example-transports/test_certs/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +xfk6H6FDZv4ao7QSrFR4hlTIz6V9/aqQkdOhsBSQyQKCAQEAzHwz4Qr5xVduGLbY +S6HV/7vHDI6Jf+3lBvqUidWa013w5yls3sZXsSckkgshRoVMszayIbystnXJMNcx +YlEDWn3iIItzHNHMKkzdOvsCETMIlvnkt6UTmK4xY+dSq4jp7Ty0N+qi8fdaCb2q +tyrYTnHHYId6bUHMBY5QZsYAaTNvYNAO96A0UaNyl42q84iTiLkJYg9SsQPad15W +7gU84Jk6rEMYdndQDvEAHpnZ1y0yA2vtySZYsbK0wj34tgTl+0/8izn7JgF4ezNH +6iQ7Z0OuDT763IrmIxBH0ZEi9YnwSYyIsr6iUYjlQIUuPFRnQYQXEdm5Xfw1pZsL +xhYoTwKCAQB9edDe4LX+0z9i4qr0iHV8H/WoyI5UD/Pc217PKkYM3+ewR9SL9D9z +TS78Sl7HgRgEmIu+MR/u5B2ePf7jkvB/oxyPwqAzJeJ72mV3Mevm27G/Ndd8lt5W +FBCGOx7ZeP4/Cv4mvPD979ix2IalDoWMSWJnpQPN+B1jGeCrUYAXQc1k/vU99gLa +8Tuu3WfBpVAsO7hAC9mu6tuLyfKVqiMOVs2aky9xLqiqW/6uIcGu+owrr+gkDDY/ +JfBSUfxYKcjtJiHOEbFGrrRe93XsngmaTz/Hv9A/QLVCuJgWEHlt4WHSc+BtAtaV +9avp6VlyVNfe4KEKW7IekrI0cmfMdXkl +-----END PRIVATE KEY-----