March 20, 2018

Getting started with IPFS using Javascript and js-ipfs

InterPlanetary File System or IPFS for short, is a peer-to-peer hypermedia protocol designed to make the web faster, safer and more open. It is a method for storing files in a distributed file system spread across different computing devices (nodes).

Once you add a file to IPFS it becomes a part of a file system distributed across the world. Each file added to IPFS gets a unique identifier, a cryptographic hash of its content, as its name. This is where IPFS differs from HTTP.
With HTTP you search for files by their location, for example, https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Ipfs-logo-1024-ice-text.png/440px-Ipfs-logo-1024-ice-text.png, and you have to trust the server that the file you are looking for is at that location and that it contains the data you want.
On the other hand, with IPFS you search for files not by their location but by their content instead. Here is the same image of IPFS logo as above obtainable by its unique hash: https://ipfs.io/ipfs/QmaqQynZ42859G5vWVLYR6W7BMAUUmwSJeu6HBEwLD5QZq.
You ask IPFS to provide the file by its content which is, after all, exactly what you as a client are interested in.
Since IPFS uses cryptographic hashes this allows for a construction of a data structure called Merkle DAG. Organizing files in this kind of a structure removes duplication of content and provides tamper resistance (if any file is tampered with it IPFS detects it).

For more details about IPFS I recommend that you visit their website at ipfs.io. Also, you should read this excellent introduction to IPFS and explanation of how files in IPFS are organized, ie. how Merkle DAG is used.

As a summary of the grand idea behind IPFS Juan Benet, the creator of IPFS wrote:
“At the bare minimum, it can be used as a global, mounted, versioned filesystem and namespace, or as the next generation file sharing system. At its best, it could push the web to new horizons, where publishing valuable information does not impose hosting it on the publisher but upon those interested, where users can trust the content they receive without trusting the peers they receive it from, and where old but important files do not go missing”.

After learning about IPFS I was interested in how one can start building software that leverages the powerful mechanisms that make up IPFS.

The team behind IPFS created a Javascript library to enable interaction between standard web apps and IPFS. That library is called js-ipfs and in this post, I will show you how easy it is to get started with building apps that interact with IPFS. We are going to build a very simple file uploader, a sort of a foundation for ” the next generation file sharing system”. This uploader will transfer files directly from your browser to IPFS.

Project Setup

This small project, which I called IPFS Box is made up out of two main files:
- index.html
- index.js

It has a single dependency js-ipfs. Bulma CSS framework is used to help organize the layout and style upload input and button elements.

For bundling the imported libraries and index.js file I used parcel (there is only a single dependency for now, but when I continue working on this project and start adding more features and dependencies I will have a bundler already prepared). To install it globally on your system with yarn or npm:

yarn global add parcel-bundler
npm install -g parcel-bundler

You can find the entire source of IPFS Box at github and have a look at the demo.

Building the Uploader (IPFS Box)

We will start by constructing a basic index.html file. It should look like the one below.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>IPFS Box</title>

    <script src="index.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
</head>

<body>
    <!-- Intro -->
    <section class="section">
        <div class="container has-text-centered">
            <img src="./undraw_collecting_fjjl.png" width="125px">
            <h1 class="title is-size-1">IPFS Box</h1>
            <h2 class="subtitle">
                Upload files to decentralized file system - IPFS
            </h2>
        </div>
    </section>
    <!-- ./Intro -->

    <!-- Uploader -->
    <section class="section" style="padding-top:0;">
        <div class="container has-text-centered">
            <div class="file is-centered is-large has-name is-boxed">
                <label class="file-label">
                    <input id="fileInput" class="file-input" type="file">
                    <span class="file-cta">
                        <span class="file-label">
                            Choose a file…
                        </span>
                    </span>
                    <span id="inputFileName" class="file-name"></span>
                </label>
            </div>
            <a id="uploadBtn" class="button is-primary is-medium" style="margin-top:50px;visibility:hidden;">Upload to IPFS</a>
            <div id="errorMsg" style="color:red;"></div>
            <div id="result"></div>
        </div>
    </section>
    <!-- ./Uploader -->
</body>

</html>

Once we are done with the HTML we can move on to index.js file. One thing to note is that the method for adding file to IPFS, ie. ipfs.files.add(data, [options], [callback]), expects data to be either one of these: Buffer instance, Readable Stream, Pull Stream, Path, URL or an array of objects of the following form:

{
  path: '/tmp/myfile.txt', // The file path
  content: <data> // A Buffer, Readable Stream or Pull Stream with the contents of the file
}
For this example we will send the file as a Buffer instance. The file index.js will look like this:

import IPFS from 'ipfs';

let fileToUpload = null;
let node = new IPFS();

node.on('ready', () => {
    console.log('Your IPFS node is ready.');

    node.version((err, version) => {
        if (err) {
            console.error('Error getting the IPFS version.', err);
            return;
        }
        console.log('version : ', version.version)
    });
});


document.addEventListener('DOMContentLoaded', e => {
    init();
});

function init() {
    setFileInput();
    setUploadBtn();
}

function setFileInput() {
    let $ = document.getElementById('fileInput');
    $.onchange = function () {
        if (!this.files || !this.files[0]) {
            return;
        }

        fileToUpload = this.files[0];

        document
            .getElementById('inputFileName')
            .innerText = fileToUpload.name;

        document
            .getElementById('uploadBtn')
            .style
            .visibility = 'visible';
    }
}

function setUploadBtn() {
    let $ = document.getElementById('uploadBtn');
    $.onclick = () => {
        prepareFile(fileToUpload);
    }

    document.addEventListener('upload-started', e => {
        $
            .classList
            .add('is-loading');
    });

    document.addEventListener('upload-done', e => {
        $
            .classList
            .remove('is-loading');
    });
}

function prepareFile(file) {
    document.dispatchEvent(new Event('upload-started'));

    let reader = new FileReader();

    reader.onload = e => {
        uploadFile(node.types.Buffer.from(e.target.result));
    }

    reader.readAsArrayBuffer(file);
}

function uploadFile(file) {
    node
        .files
        .add(file, (err, fileAdded) => {
            document.dispatchEvent(new Event('upload-done'));

            if (err) {
                displayError(err);
                return;
            }

            document
                .getElementById('result')
                .innerHTML = '<a id="viewFileBtn" href="https://ipfs.io/ipfs/' + fileAdded[0].hash + '" target="_blank" class="button is-info is-medium" style="margin-top:20px;">View' +
                    ' file</a>';
        });
}

function displayError(err) {
    document
        .getElementById('errorMsg')
        .innerText = err;
}

After completing the index.js file we run the following command to tell parcel to bundle index.js and it’s dependencies:

parcel index.html

// This will result in:
Server running at http://localhost:1234
  Built in Xs.

Once we visit http://localhost:1234 our IPFS Box uploader should be ready to work and transfer our files to IPFS. The page is now ready for uploading the files. It will look like this: alt text

Conclusion

I hope that you have learned something new and are inspired to go create something that uses the full power of IPFS.
Before that I recommend that you check out docs at their repo where you will also find some more examples of js-ipfs usage. Also have a look at awesome-ipfs repo and see how various projects are using IPFS.
I am currently making a prototype for a project that will use IPFS along with React, Next.js, and Typescript, and will definitely learn more about this technology and probably write more on this topic.

© Harun Đulić 2018