diff --git a/examples/exchange-files-in-browser/README.md b/examples/exchange-files-in-browser/README.md index 502cdff1aa..5683089fd4 100644 --- a/examples/exchange-files-in-browser/README.md +++ b/examples/exchange-files-in-browser/README.md @@ -1,14 +1,14 @@ -# Tutorial - Transfer files between the browser and other IPFS nodes +# Exchange files between the browser and other IPFS nodes -> Welcome! This tutorial will help you exchange files between browser nodes and go-ipfs nodes. +This tutorial will help you exchange files between browser nodes and go-ipfs or js-ipfs nodes! -caveat: js-ipfs currently doesn't support DHT peer discovery, the peer from which you are fetching data should be within the reach (local or in public IP) of the browser node. +**Note:** As `js-ipfs@0.29.0` currently doesn't support DHT peer discovery, the peer from which you are fetching data should be within the reach (local or in public IP) of the browser node. -That being said, we will explain throughout this tutorial to circunvent the caveats and once they are fixed, we will update the tutorial as well. +That being said, we will explain how to circumvent these caveats and once they are fixed, we'll update the tutorial as well. ## Application diagram -The goal of this tutorial is to create a application with a IPFS node that dials to other instances of it using WebRTC, and at the same time dial and transfer files from a Desktop IPFS node using WebSockets as the transport. +The goal of this tutorial is to create a simple application with an IPFS node that dials to other instances using WebRTC, and at the same time dial and transfer files from a browser IPFS node using WebSockets as the transport. ``` ┌──────────────┐ ┌──────────────┐ @@ -23,43 +23,41 @@ The goal of this tutorial is to create a application with a IPFS node that dials └──────────────┘ ``` -## Check out the final state +## Tutorial goal -In the end, you should get an app running, something like this: +The goal of this tutorial is for you to have something like this in the end: -![](https://ipfs.io/ipfs/Qmbti2nBZWxQLhpggB7tC3HvcxTMivmMo3MVwQveAsHBAE) +![](img/goal.png) ## Step-by-step instructions -Here's what we are going to be doing, today: +Here's what we are going to be doing: -- 1. Set up, install a go-ipfs node in your machine -- 2. Make your daemons listen on WebSockets -- 3. Start the WebApp -- 4. Dial to a node using WebSockets (your Desktop ones) -- 5. Transfer files between all of your nodes, have fun! +1. Install a `go-ipfs` or `js-ipfs` node in your machine +2. Make your daemons listen on WebSockets +3. Start the app +4. Dial to a node using WebSockets (your desktop ones) +5. Transfer files between all of your nodes! -Let's go. +Just follow the instructions below and it will be up and running in no time! -### 1. Set up +### 1. Install `go-ipfs` or `js-ipfs` -You'll need to have an implementation of IPFS running on your machine. Currently, this means either go-ipfs or js-ipfs. +If you already have `go-ipfs` or `js-ipfs` installed in your machine, you can skip this step. Otherwise, read on. -Installing go-ipfs can be done by installing the binary [here](https://ipfs.io/ipns/dist.ipfs.io/#go-ipfs). Alternatively, you could follow the instructions in the README at [ipfs/go-ipfs](https://github.com/ipfs/go-ipfs). +This tutorial works with either `go-ipfs` or `js-ipfs`, so you can install one of your choosing. -Installing js-ipfs requires you to have node and [npm](https://www.npmjs.com). Then, you simply run: +`go-ipfs` can be installed via its binary [here](https://ipfs.io/ipns/dist.ipfs.io/#go-ipfs). Alternatively, you can follow the install instructions in [ipfs/go-ipfs](https://github.com/ipfs/go-ipfs#install). + +`js-ipfs` requires you to have [node and npm](https://www.npmjs.com/get-npm) installed. Then, you simply run: ```sh > npm install --global ipfs -... -> jsipfs --help -Commands: -... ``` This will alias `jsipfs` on your machine; this is to avoid issues with `go-ipfs` being called `ipfs`. -At this point, you have either js-ipfs or go-ipfs running. Now, initialize it: +At this point, you should have either `js-ipfs` or `go-ipfs` running. Now, initialize it: ```sh > ipfs init @@ -67,15 +65,15 @@ At this point, you have either js-ipfs or go-ipfs running. Now, initialize it: > jsipfs init ``` -This will set up your IPFS repo in your home directory. +This will set up an IPFS repo in your home directory. ### 2. Make your daemons listen on WebSockets -At this point, you need to edit your `config` file, the one you just set up with `{js}ipfs init`. It should be in either `~/.jsipfs/config` or `~/.ipfs/config`, depending on whether you're using JS or Go. +Now you need to edit your `config` file, the one you just set up with `{js}ipfs init`. It should be in either `~/.jsipfs/config` or `~/.ipfs/config`, depending on whether you're using JS or Go. -Note: js-ipfs sets up a websocket listener by default, if you are just using js-ipfs you can skip this test. +**Note:** `js-ipfs` sets up a websocket listener by default, so if you're using the JS implementation you can skip this and just start the daemon. -Since websockets support is currently not on by default, you'll need to add a WebSockets address manually. Look into your config file and find the `Addresses` section: +Since websockets support is currently not on by default, you'll need to add a WebSockets address manually. Look into your config file to find the `Addresses` section: ```json "Addresses": { @@ -87,7 +85,7 @@ Since websockets support is currently not on by default, you'll need to add a We } ``` -Add the following entry to your `Swarm` array: `/ip4/127.0.0.1/tcp/9999/ws`. Now, it should look like this: +Add the `/ip4/127.0.0.1/tcp/9999/ws` entry to your `Swarm` array. Now it should look like this: ```json "Addresses": { @@ -100,14 +98,14 @@ Add the following entry to your `Swarm` array: `/ip4/127.0.0.1/tcp/9999/ws`. Now } ``` -Now it should listen on Websockets. We're ready to start the daemon. +Save the file and it should be able to listen on Websockets. We're ready to start the daemon. ```sh > ipfs daemon +# or +> jsipfs daemon ``` -(Again, either `jsipfs` or `ipfs` works. I'll stop repeting this from here on out.) - You should see the Websocket address in the output: ```sh @@ -121,39 +119,85 @@ Gateway (readonly) server listening on /ip4/0.0.0.0/tcp/8080 Daemon is ready ``` -It's there in line 5 - see the `/ws`? Good. that means it is listening. +Check the `/ws` in line 5, that means it is listening. Cool. -### 3. Start the WebApp project +### 3. Start the app -Now, you'll need to make sure you are in `js-ipfs/examples/exchange-files-in-browser`. You'll see a `package.json`: this manifest holds the information for which packages you'll need to install to run the webapp. Let's install them, and then start the project: +Make sure you're in `js-ipfs/examples/exchange-files-in-browser`. + +We'll need to install and bundle the dependencies to run the app. Let's do it: ```sh > npm install +... +> npm run bundle +... > npm start ``` -You should see this text: +You should see something like this if all went well: ```sh Starting up http-server, serving public Available on: http://127.0.0.1:12345 - http://192.168.1.24:12345 + http://192.168.2.92:12345 Hit CTRL-C to stop the server ``` -Go to http://127.0.0.1:12345 in your browser; you're now in the webapp, if all went well. +Now go to http://127.0.0.1:12345 in a modern browser and you're on! + +### 4. Dial to a node using WebSockets (your desktop ones) + +Make sure you have a daemon running. If you don't, run: + +```sh +> ipfs daemon +# or +> jsipfs daemon +``` + +Open another terminal window to find the websocket addresses that it is listening on: + +```sh +> ipfs id +# or +> jsipfs id +``` + +It should look like this: `/ip4/127.0.0.1/tcp/9999/ws/ipfs/`. + +Copy and paste the *multiaddr* to connect to that peer: -### 4. Dial to a node using WebSockets (your Desktop ones) +![](img/connect-1.png) -On your terminal, run `ipfs id` to find the WebSockets address that it is listening on. Should look like this: `"/ip4/127.0.0.1/tcp/4003/ws/ipfs/". Important, your node must be running in order to have listeners, to do so, run in another tab of your terminal: `ipfs daemon`. +Check that you got connected: -![](https://ipfs.io/ipfs/Qme9RM3SSyb57PGA7n5bEhwhMwS8fDrMZ8zzKkrwncRcfm) -![](https://ipfs.io/ipfs/QmdFX4wJkKpryisjGQGt88Yr8zaQM9zMPL3xzK2YGTUMNM) +![](img/connect-2.png) -### 5. Transfer files between all of your nodes, have fun! +### 5. Transfer files between all of your nodes! + +Now you can add files through the CLI with: + +```sh +> ipfs add +# or +> jsipfs add +``` + +Copy and paste the *multihash* and fetch the file in the browser! + +![](img/fetch.png) + +You can also open two browser tabs, drag and drop files in one of them, and fetch them in the other! + +But the coolest thing about this tutorial is `pubsub`! You can open two tabs that will share files through workspaces named after the url. Try opening two tabs with the following url: + +``` +http://127.0.0.1:12345/#file-exchange +# You can substitute `file-exchange` with anything you like, just make sure the two tabs are in the same workspace. +``` -Now you can drag an drop files on the browser or add them through the CLI with `ipfs add ` and with the fetch file box, you can retrieve the file to the browser or other browser tabs! +Now every file that you upload in one tab will appear in the other! You can even open a new tab in that workspace and it will sync the files that were added before! -![](https://ipfs.io/ipfs/QmcVNbhmMFzz9x2mY33GPGetibFGXXD7dYd3kDa7eKEUyw) -![](https://ipfs.io/ipfs/QmZcRvGQtM7mnSWKqFwptCYoBitBJaGBKLLjvzENfzXFMi) +![](img/pubsub.png) diff --git a/examples/exchange-files-in-browser/img/connect-1.png b/examples/exchange-files-in-browser/img/connect-1.png index cb89afbfed..81dd25d9a2 100644 Binary files a/examples/exchange-files-in-browser/img/connect-1.png and b/examples/exchange-files-in-browser/img/connect-1.png differ diff --git a/examples/exchange-files-in-browser/img/connect-2.png b/examples/exchange-files-in-browser/img/connect-2.png index 3f2c27320e..a481be379d 100644 Binary files a/examples/exchange-files-in-browser/img/connect-2.png and b/examples/exchange-files-in-browser/img/connect-2.png differ diff --git a/examples/exchange-files-in-browser/img/fetch-files-1.png b/examples/exchange-files-in-browser/img/fetch-files-1.png deleted file mode 100644 index c702b64972..0000000000 Binary files a/examples/exchange-files-in-browser/img/fetch-files-1.png and /dev/null differ diff --git a/examples/exchange-files-in-browser/img/fetch-files-2.png b/examples/exchange-files-in-browser/img/fetch-files-2.png deleted file mode 100644 index 02d17c5be5..0000000000 Binary files a/examples/exchange-files-in-browser/img/fetch-files-2.png and /dev/null differ diff --git a/examples/exchange-files-in-browser/img/fetch.png b/examples/exchange-files-in-browser/img/fetch.png new file mode 100644 index 0000000000..2bcfef1d9d Binary files /dev/null and b/examples/exchange-files-in-browser/img/fetch.png differ diff --git a/examples/exchange-files-in-browser/img/final.png b/examples/exchange-files-in-browser/img/final.png deleted file mode 100644 index f6f61e98fa..0000000000 Binary files a/examples/exchange-files-in-browser/img/final.png and /dev/null differ diff --git a/examples/exchange-files-in-browser/img/goal.png b/examples/exchange-files-in-browser/img/goal.png new file mode 100644 index 0000000000..50fb3d5a94 Binary files /dev/null and b/examples/exchange-files-in-browser/img/goal.png differ diff --git a/examples/exchange-files-in-browser/img/pubsub.png b/examples/exchange-files-in-browser/img/pubsub.png new file mode 100644 index 0000000000..9fc39b464a Binary files /dev/null and b/examples/exchange-files-in-browser/img/pubsub.png differ diff --git a/examples/exchange-files-in-browser/package.json b/examples/exchange-files-in-browser/package.json index 2e2b24a4af..c8d4ad595b 100644 --- a/examples/exchange-files-in-browser/package.json +++ b/examples/exchange-files-in-browser/package.json @@ -3,16 +3,16 @@ "version": "0.0.0", "scripts": { "bundle": "browserify public/app.js > public/bundle.js", - "dev": "npm run bundle && npm run start", - "start": "http-server -c-1 -p 12345 public" + "start": "http-server -c-1 -p 12345 public", + "dev": "npm run bundle && npm run start" }, "license": "MIT", "devDependencies": { - "browserify": "^14.4.0", - "http-server": "~0.10.0" + "browserify": "^16.2.0", + "http-server": "^0.11.1" }, "dependencies": { - "stream-buffers": "^3.0.1", - "ipfs": "file:../../" + "ipfs": "0.29.0", + "stream-buffers": "^3.0.1" } } diff --git a/examples/exchange-files-in-browser/public/app.css b/examples/exchange-files-in-browser/public/app.css index 32667fc685..61b04121d0 100644 --- a/examples/exchange-files-in-browser/public/app.css +++ b/examples/exchange-files-in-browser/public/app.css @@ -1,3 +1,8 @@ + +/* =========================================================================== + Layout + =========================================================================== */ + * { box-sizing: border-box; } @@ -5,8 +10,10 @@ body { min-height: 100vh; font-family: sans-serif; - color: white; - background: linear-gradient(to bottom,#041727 0%,#062b3f 100%); + color: #0B3A53; + background: #141E30; /* Fallback for old browsers */ + background: -webkit-linear-gradient(to top, #243B55, #141E30); /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient(to top, #243B55, #141E30); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ pointer-events: auto; padding: 0; margin: 0; @@ -31,83 +38,61 @@ h3 { font-weight: 700; } -main, -header { - filter: none; -} - -.dragover-popup { - position: fixed; - top: 0.5em; - left: 0.5em; - width: calc(100% - 1em); - height: calc(100% - 1em); - background-color: rgba(0, 0, 0, 0.5); - display: none; - pointer-events: none; -} - -.dragover-popup h1 { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%) -} - -body.dragging main, -body.dragging header { - filter: blur(5px); -} - -body.dragging .dragover-popup { - display: block; -} - header { - text-align: center; display: flex; justify-content: center; align-items: center; - padding: 3em 0; + padding: 2em 0; } -ul { - margin: 0; - padding: 0; - list-style: none; +main { + width: 90%; + margin: 0 auto; } -ul li { - margin: 1em 0; - font-size: 0.7em; - font-family: monospace; - word-wrap: break-word; +.columns { + display: flex; + justify-content: space-between; } + +/* =========================================================================== + Buttons & Inputs + =========================================================================== */ + button { - background-color: rgba(0,0,0,0.2); - color: #6acad1; - border: 2px solid #6acad1; - font-size: 1em; padding: 0.625em 1.5em; - border-radius: 0.125em; - margin: .5em 0; + border: 2px solid #69C4CD; + border-radius: 2px; + background-color: #69C4CD; + color: #FFF; + font-size: 0.9em; + cursor: pointer; + outline: none; +} + +button:hover { + opacity: 0.9; } -button.full { - margin-right: 0; - margin-left: 0; +input { width: 100%; + margin-right: 3px; + padding: 0.7em; + border: 2px solid rgba(0, 0, 0, 0.2); + border-radius: 2px; + color: #000; + outline: none; } -button:hover { - color: white; - border: 2px solid white; - cursor: pointer; +input:hover, +input:focus, +input:active { + border-color: #69C4CD; } -.address { - font-family: monospace +.input-button { + display: flex; } .disabled *, @@ -116,132 +101,198 @@ button:disabled { opacity: 0.2; } -input { +/* =========================================================================== + Tables + =========================================================================== */ + +table { width: 100%; - border: 2px solid rgba(0,0,0,0.2); - color: black; - padding: 0.7em; - border-radius: 2px; + margin: 1em 0; + padding: 1em; + border-spacing: 0; + background-color: #FFF; + word-break: break-all; } -input:hover, input:focus, input:active { - border-color: #6acad1; +table th, +table td { + padding: 1em; + font-size: 0.8em; + text-align: left; + font-weight: normal; } -input, button { - outline: none; +table th { + padding-top: 0; + font-size: 0.7em; + text-transform: uppercase; + opacity: 0.5; } -main { - width: 90%; - margin: 0 auto; +table tbody tr:nth-child(odd) { + background-color: #F7F8FA; } -.buttons, -.columns { - display: flex; - flex-direction: row; - justify-content: space-between; +table tbody tr:hover { + background-color: rgb(100, 196, 205); + background-color: rgba(100, 196, 205, 0.2); } -.buttons > button, -.columns > div { - width: calc(50% - 0.5em); +.table-action { + opacity: 0.5; } -.buttons > button { - margin: 0 0 1em; +.table-action:hover { + opacity: 1; } +/* =========================================================================== + Box + =========================================================================== */ + .box { - background-color: rgba(255, 255, 255, 0.05); - padding: 1em; + display: flex; + flex-direction: column; margin-bottom: 1em; + padding: 1em; + border-radius: 2px; + background-color: #F7F8FA; } .box > h2 { display: block; - border-bottom: 2px solid rgba(255, 255, 255, 0.1); - border-right: 0; - border-left: 0; + border-bottom: 1px solid #B7BBC8; text-align: center; padding-bottom: 0.5em; margin-bottom: 1em; } -#peers i { - display: none; - margin-top: 5px; - margin-bottom: 5px; - font-size: 14px; +/* Node + =========================================================================== */ + +.box.node > div { + display: flex; + margin-bottom: 1em; } -#peers.waiting i { - display: block; +.box.node > div:last-child { + margin-bottom: 0; } -#errors { - grid-area: errors; - color: red; - font-style: italic; - font-size: 1.125em; - display: block; - margin: 0 0 1em; +.box.node > div > h3 { + flex: 0 0 15%; } -#errors.hidden { - display: none; +.box.node > div pre, +.box.node > div > ul { + flex: 0 0 85%; + margin: 0; } -#note { - position: absolute; - top: 1em; - right: 1em; - font-size: 0.625em; +.box.node ul { + margin: 0; + padding: 0; + list-style: none; +} + +.node-addresses li { + margin-bottom: 0.5em; + font-size: 0.7em; + word-wrap: break-word; +} + +#logs { + margin: 0; + color: #EA5037; +} + +#logs.success { + color: #0CB892; +} + +/* Peers + =========================================================================== */ + +#peers { + flex: 0 0 calc(35% - 1em); +} + +/* Files + =========================================================================== */ + +#files { + flex: 0 0 65%; + min-height: 500px; } #file-status { font-style: italic; - color: cyan; + color: #6ACAD1; font-size: 0.8em; margin: 1em 0; } -table { - width: 100%; - margin: 1em 0; - word-break: break-all; - border-collapse: collapse; +#files table th:nth-child(1), +#files table td:nth-child(1) { + width: 35%; } -table thead { - background-color: rgba(255, 255, 255, 0.1); - font-weight: normal +#files table th:nth-child(2), +#files table td:nth-child(2) { + width: 50%; } -table th, table td { - padding: 0.5em 0; +#files table th:nth-child(3), +#files table td:nth-child(3) { + width: 15%; } -table td:last-child, -table th:last-child { - width: 20%; +#files .empty-row td { text-align: center; + opacity: 0.5; } -table td:first-child { - width: 45%; +#drag-container { + height: 10em; + margin-top: 1em; + display: flex; + justify-content: center; + align-items: center; + border: 1px dashed; + border-radius: 2px; + opacity: 0.7; } -table td:nth-child(2) { - width: 35%; - font-family: monospace; +#drag-container.dragging { + background-color: #E7E8EE; + opacity: 1; + } -table tr:hover { - background-color: rgba(0, 0, 0, 0.2) +#drag-container p { + margin-left: 1em; } -table a { - color: inherit; -} \ No newline at end of file +#progress-container { + margin-top: 1em; + border-radius: 0.5em; + background-color: #0B3A53; + overflow-x: hidden; +} + +#progress-bar { + height: 1em; + border-radius: 0.5em; + background-color: #6ACAD1; + transform: translateX(-100%); +} + +/* =========================================================================== + Responsiveness + =========================================================================== */ + +@media (max-width: 768px) { + .columns { + flex-direction: column-reverse; + } +} diff --git a/examples/exchange-files-in-browser/public/app.js b/examples/exchange-files-in-browser/public/app.js index d18114a16e..cb38e3546c 100644 --- a/examples/exchange-files-in-browser/public/app.js +++ b/examples/exchange-files-in-browser/public/app.js @@ -1,71 +1,120 @@ -/* global self */ +/* global location */ 'use strict' -const $startButton = document.querySelector('#start') -const $stopButton = document.querySelector('#stop') +const IPFS = require('ipfs') + +// Node +const $nodeId = document.querySelector('.node-id') +const $nodeAddresses = document.querySelector('.node-addresses') +const $logs = document.querySelector('#logs') +// Peers const $peers = document.querySelector('#peers') -const $peersList = $peers.querySelector('ul') -const $errors = document.querySelector('#errors') +const $peersList = $peers.querySelector('tbody') +const $multiaddrInput = document.querySelector('#multiaddr-input') +const $connectButton = document.querySelector('#peer-btn') +// Files +const $multihashInput = document.querySelector('#multihash-input') +const $fetchButton = document.querySelector('#fetch-btn') +const $dragContainer = document.querySelector('#drag-container') +const $progressBar = document.querySelector('#progress-bar') const $fileHistory = document.querySelector('#file-history tbody') -const $fileStatus = document.querySelector('#file-status') -const $multihashInput = document.querySelector('#multihash') -const $catButton = document.querySelector('#cat') -const $connectPeer = document.querySelector('#peer-input') -const $connectPeerButton = document.querySelector('#peer-btn') -const $body = document.querySelector('body') -const $idContainer = document.querySelector('.id-container') -const $addressesContainer = document.querySelector('.addresses-container') +const $emptyRow = document.querySelector('.empty-row') +// Misc const $allDisabledButtons = document.querySelectorAll('button:disabled') const $allDisabledInputs = document.querySelectorAll('input:disabled') const $allDisabledElements = document.querySelectorAll('.disabled') +const FILES = [] +const workspace = location.hash + +let fileSize = 0 + let node let info let Buffer -/* - * Start and stop the IPFS node - */ +/* =========================================================================== + Start the IPFS node + =========================================================================== */ function start () { if (!node) { - updateView('starting', node) - const options = { - repo: 'ipfs-' + Math.random() + Date.now().toString(), + EXPERIMENTAL: { + pubsub: true + }, + repo: 'ipfs-' + Math.random(), config: { Addresses: { - Swarm: [ - // '/dns4/wrtc-star.discovery.libp2p.io/tcp/443/wss/p2p-webrtc-star' - '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star' - ] + Swarm: ['/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star'] } } } - // IFDEV: To test with latest js-ipfs - // const IPFS = require('ipfs') - // node = new IPFS(options) - // VEDIF - - // EXAMPLE - node = new self.Ipfs(options) + node = new IPFS(options) Buffer = node.types.Buffer - node.once('start', () => node.id((err, id) => { - if (err) { return onError(err) } + node.once('start', () => { + node.id() + .then((id) => { + info = id + updateView('ready', node) + onSuccess('Node is ready.') + setInterval(refreshPeerList, 1000) + setInterval(sendFileList, 10000) + }) + .catch((error) => onError(error)) + + subscribeToWorkpsace() + }) + } +} + +/* =========================================================================== + Pubsub + =========================================================================== */ + +const messageHandler = (message) => { + const myNode = info.id + const hash = message.data.toString() + const messageSender = message.from - info = id - updateView('ready', node) - setInterval(refreshPeerList, 1000) - $peers.classList.add('waiting') - })) + // append new files when someone uploads them + if (myNode !== messageSender && !isFileInList(hash)) { + $multihashInput.value = hash + getFile() } } -function stop () { - window.location.href = window.location.href // refresh page +const subscribeToWorkpsace = () => { + node.pubsub.subscribe(workspace, messageHandler) + .catch(() => onError('An error occurred when subscribing to the workspace.')) +} + +const publishHash = (hash) => { + const data = Buffer.from(hash) + + node.pubsub.publish(workspace, data) + .catch(() => onError('An error occurred when publishing the message.')) +} + +/* =========================================================================== + Files handling + =========================================================================== */ + +const isFileInList = (hash) => FILES.indexOf(hash) !== -1 + +const sendFileList = () => FILES.forEach((hash) => publishHash(hash)) + +const updateProgress = (bytesLoaded) => { + let percent = 100 - ((bytesLoaded / fileSize) * 100) + + $progressBar.style.transform = `translateX(${-percent}%)` +} + +const resetProgress = () => { + $progressBar.style.transform = 'translateX(-100%)' } function appendFile (name, hash, size, data) { @@ -77,53 +126,65 @@ function appendFile (name, hash, size, data) { nameCell.innerHTML = name const hashCell = document.createElement('td') - const link = document.createElement('a') - link.innerHTML = hash - link.setAttribute('href', url) - link.setAttribute('download', name) - hashCell.appendChild(link) + hashCell.innerHTML = hash const sizeCell = document.createElement('td') sizeCell.innerText = size + const downloadCell = document.createElement('td') + const link = document.createElement('a') + link.setAttribute('href', url) + link.setAttribute('download', name) + link.innerHTML = 'Download' + downloadCell.appendChild(link) + row.appendChild(nameCell) row.appendChild(hashCell) row.appendChild(sizeCell) + row.appendChild(downloadCell) $fileHistory.insertBefore(row, $fileHistory.firstChild) + + publishHash(hash) } function getFile () { - const cid = $multihashInput.value + const hash = $multihashInput.value $multihashInput.value = '' - $errors.classList.add('hidden') - - if (!cid) { return console.log('no multihash was inserted') } + if (!hash) { + return onError('No multihash was inserted.') + } else if (isFileInList(hash)) { + return onSuccess('The file is already in the current workspace.') + } - node.files.get(cid, (err, files) => { - if (err) { return onError(err) } + FILES.push(hash) - files.forEach((file) => { - if (file.content) { - appendFile(file.name, cid, file.size, file.content) - } + node.files.get(hash) + .then((files) => { + files.forEach((file) => { + if (file.content) { + appendFile(file.name, hash, file.size, file.content) + onSuccess(`The ${file.name} file was added.`) + $emptyRow.style.display = 'none' + } + }) }) - }) + .catch(() => onError('An error occurred when fetching the files.')) } -/* - * Drag and drop - */ +/* Drag & Drop + =========================================================================== */ + +const onDragEnter = () => $dragContainer.classList.add('dragging') + +const onDragLeave = () => $dragContainer.classList.remove('dragging') + function onDrop (event) { - onDragExit() - $errors.classList.add('hidden') + onDragLeave() event.preventDefault() - if (!node) { - return onError('IPFS must be started before files can be added') - } const dt = event.dataTransfer const filesDropped = dt.files @@ -143,71 +204,78 @@ function onDrop (event) { files.forEach((file) => { readFileContents(file) .then((buffer) => { + fileSize = file.size + node.files.add({ path: file.name, content: Buffer.from(buffer) - }, { wrap: true }, (err, filesAdded) => { - if (err) { return onError(err) } + }, { wrap: true, progress: updateProgress }, (err, filesAdded) => { + if (err) { + return onError(err) + } - $multihashInput.value = filesAdded[0].hash - $fileStatus.innerHTML = `${file.name} added! Try to hit 'Fetch' button!` + // As we are wrapping the content we use that hash to keep + // the original file name when adding it to the table + $multihashInput.value = filesAdded[1].hash + + resetProgress() + getFile() }) }) .catch(onError) }) } -/* - * Network related functions - */ - -// Get peers from IPFS and display them +/* =========================================================================== + Peers handling + =========================================================================== */ function connectToPeer (event) { - event.target.disabled = true - node.swarm.connect($connectPeer.value, (err) => { - if (err) { return onError(err) } + const multiaddr = $multiaddrInput.value - $connectPeer.value = '' + if (!multiaddr) { + return onError('No multiaddr was inserted.') + } - setTimeout(() => { - event.target.disabled = false - }, 500) - }) + node.swarm.connect(multiaddr) + .then(() => { + onSuccess(`Successfully connected to peer.`) + $multiaddrInput.value = '' + }) + .catch(() => onError('An error occurred when connecting to the peer.')) } function refreshPeerList () { - node.swarm.peers((err, peers) => { - if (err) { - return onError(err) - } - const peersAsHtml = peers - .map((peer) => { - if (peer.addr) { - const addr = peer.addr.toString() - if (addr.indexOf('ipfs') >= 0) { - return addr - } else { - return addr + peer.peer.id.toB58String() + node.swarm.peers() + .then((peers) => { + const peersAsHtml = peers.reverse() + .map((peer) => { + if (peer.addr) { + const addr = peer.addr.toString() + if (addr.indexOf('ipfs') >= 0) { + return addr + } else { + return addr + peer.peer.id.toB58String() + } } - } - }) - .map((addr) => { - return '
  • ' + addr + '
  • ' - }).join('') - - if (peers.length === 0) { - $peers.classList.add('waiting') - } else { - $peers.classList.remove('waiting') + }) + .map((addr) => { + return `${addr}` + }).join('') + $peersList.innerHTML = peersAsHtml - } - }) + }) + .catch((error) => onError(error)) } -/* - * UI functions - */ +/* =========================================================================== + Error handling + =========================================================================== */ + +function onSuccess (msg) { + $logs.classList.add('success') + $logs.innerHTML = msg +} function onError (err) { let msg = 'An error occured, check the dev console' @@ -218,38 +286,26 @@ function onError (err) { msg = err } - $errors.innerHTML = msg - $errors.classList.remove('hidden') + $logs.classList.remove('success') + $logs.innerHTML = msg } window.onerror = onError -function onDragEnter () { - $body.classList.add('dragging') -} +/* =========================================================================== + App states + =========================================================================== */ -function onDragExit () { - $body.classList.remove('dragging') -} - -/* - * App states - */ const states = { ready: () => { const addressesHtml = info.addresses.map((address) => { - return '
  • ' + address + '
  • ' + return `
  • ${address}
  • ` }).join('') - $idContainer.innerText = info.id - $addressesContainer.innerHTML = addressesHtml + $nodeId.innerText = info.id + $nodeAddresses.innerHTML = addressesHtml $allDisabledButtons.forEach(b => { b.disabled = false }) $allDisabledInputs.forEach(b => { b.disabled = false }) $allDisabledElements.forEach(el => { el.classList.remove('disabled') }) - $stopButton.disabled = false - $startButton.disabled = true - }, - starting: () => { - $startButton.disabled = true } } @@ -261,19 +317,20 @@ function updateView (state, ipfs) { } } -/* - * Boot this application! - */ +/* =========================================================================== + Boot the app + =========================================================================== */ + const startApplication = () => { // Setup event listeners - $body.addEventListener('dragenter', onDragEnter) - $body.addEventListener('drop', onDrop) - $body.addEventListener('dragleave', onDragExit) - - $startButton.addEventListener('click', start) - $stopButton.addEventListener('click', stop) - $catButton.addEventListener('click', getFile) - $connectPeerButton.addEventListener('click', connectToPeer) + $dragContainer.addEventListener('dragenter', onDragEnter) + $dragContainer.addEventListener('dragover', onDragEnter) + $dragContainer.addEventListener('drop', onDrop) + $dragContainer.addEventListener('dragleave', onDragLeave) + $fetchButton.addEventListener('click', getFile) + $connectButton.addEventListener('click', connectToPeer) + + start() } startApplication() diff --git a/examples/exchange-files-in-browser/public/assets/download.svg b/examples/exchange-files-in-browser/public/assets/download.svg new file mode 100644 index 0000000000..ca28c5d939 --- /dev/null +++ b/examples/exchange-files-in-browser/public/assets/download.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/exchange-files-in-browser/public/ipfs-logo.svg b/examples/exchange-files-in-browser/public/assets/ipfs-logo.svg similarity index 100% rename from examples/exchange-files-in-browser/public/ipfs-logo.svg rename to examples/exchange-files-in-browser/public/assets/ipfs-logo.svg diff --git a/examples/exchange-files-in-browser/public/assets/upload.svg b/examples/exchange-files-in-browser/public/assets/upload.svg new file mode 100644 index 0000000000..6e2f818a24 --- /dev/null +++ b/examples/exchange-files-in-browser/public/assets/upload.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/exchange-files-in-browser/public/index.html b/examples/exchange-files-in-browser/public/index.html index 0e3385cb45..b753caf3a2 100644 --- a/examples/exchange-files-in-browser/public/index.html +++ b/examples/exchange-files-in-browser/public/index.html @@ -3,56 +3,72 @@ - IPFS - Transfer Files + IPFS - Exchange Files -
    -

    Drop file to upload

    -
    -
    -

    IPFS | InterPlanetary File Exchange

    - P.S. drop files anywhere to upload them! + IPFS
    - +
    +

    Node

    -
    -
    -

    Your daemon

    -
    - - -
    -
    -

    ID

    -
    N/A
    -

    Addresses

    -
      -
    • Not yet online
    • -
    -
    +

    ID

    +
    
    +        
    + +
    +

    Addresses

    +
      +
      + +
      +

      Logs

      +
      +
      Initializing node...
      +
      +
      + -
      -

      Peers

      - - - Waiting for peers... -
        +
        +
        +

        Peers

        + +
        + +
        + + + + + + + + +
        Connected Peers
        -
        +

        Files

        - - - -

        +
        + + +
        + +
        + Upload +

        Drag & drop a file to upload it.

        +
        + +
        +
        +
        @@ -62,14 +78,17 @@

        Files

        - + + + + +
        Size
        There are no files in this workspace.
        +
        - - - - + +