EveryBit.js tutorial

Build a sample web app with EveryBit.js

Overview

In this tutorial we use EveryBit.js to build Songr, a sample web app for sharing audio clips with friends. We'll provide users of Songr with the ability to:
  • Register new accounts and login
  • Manage a group of friends
  • Publish song clips privately to friends, using encryption
  • View clips that have been shared with them
You'll find all the code needed for this sample website at GitHub.

Install

Method 1 - Use the CDN

Copy and paste the script tag into the html file in your project:

<script src="https://everybit.com/everybit-min.js"></script>

Method 2 - Visit the GitHub Repository

Alternatively, you can download the code from our GitHub repository.

Setup

Make index.html

Let's create the basic layout for our HTML file.
<!doctype html>
<html>
<head>
    <title>Songr</title>
    <script src="http://everybit.com/everybit-min.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>

</body>
</html>
As you can see, we've included jQuery in our tutorial to make managing the user interface simpler, but it is not required to use EveryBit.

Initializing the system

Now that we have our basic HTML, lets write some JavaScript. We'll use songr.js to hold our code:
<script src="js/songr.js"></script>

The first thing we need to do in songr.js is turn on EveryBit.js
var options = {enableP2P: false};
EB.init(options);

Next we add support for displaying mp3's
EB.M.Forum.addContentType('audio', {
    toHtml: function(content) {
        return '<audio controls><source src=' + content + ' type="audio/mpeg"></audio>';
    }
});
This handler generates the HTML code needed to display audio files in the browser after they have been decrypted. See how easy it is to add support for additional content types to your app?

Managing Users

For our Songr app, we will provide 3 user options:
  • Sign up
  • Login
  • Sign out

Sign up

User registration is vital any new app, so we'll deal with it first.
var prom = EB.createIdentity(requestedUser, password);

Usernames can only contain letters and numbers and must be at least 10 characters long. For information about registering shorter usernames, send a message to user everybit.

At a technical level, EB.createIdentity converts the supplied password into a private and a public key. It uses the private key to sign a message containing the requested username and public key. Neither private keys nor passwords are ever sent over the network. If registration is successful, this new user will automatically be logged in.

Note: To make network calls EveryBit.js makes use of asynchronous function calls. These will return a promise. Next actions can be called with:
prom.then(function(results){ // handle success })
and errors caught with
.catch(function(err) {// error })

Login

Since EveryBit does its user management client-side, "Logging in" really means checking to make sure the user's password generates the same public key listed for that username.

To verify a username and password combination we call:

var prom = EB.loginWithPassphrase(username, password);

This function call handles several steps for us:

  1. Convert the supplied password to private and then public keys
  2. Request the most recent version of the public username record
  3. Check to make sure the locally generated public key matches the one found on the network
  4. Return a message based on the success of the user record lookup and comparison

Sign out

To sign a user out, we need to remove that user's profile information and private content from the browser's local storage.
EB.removeIdentity(username);
EB.Data.removeAllPrivateShells();

EB.removeIdentity removes the current account's private keys from the browser's storage and EB.Data.removeAllPrivateShells removes all currently cached user content.

Building the contact list

Since Songr is a social app, we'll create the functions that allow users to add and remove friends.

Keeping track of friends

We need somewhere to store information about a user's friends. Every user in the EveryBit.js system can store preferences and other private information to an encrypted identity file. To manage the content of that file, we use the "preferences" field. The function below gets a users current preferences.
function getPrefs() {
    var prefs = {};
    EB.useSecureInfo(function(identities,currentUsername) {
        prefs = identities[currentUsername].preferences;
    });
    return prefs;
}
                        
The function EB.useSecureInfo adds a layer of security to user data by preventing accidental leakage. Once inside, we can copy the preferences for the current username and return them.

Displaying friends

Since we can store friends, we probably want a way to get and display them to the user. Let's build a helper function to get the list of friends a user has.

function getFriends() {
    var prefs = getPrefs();

    if (!prefs.friends || prefs.friends.length == 0) {
        EB.setPreference("friends",[EB.getCurrentUsername()]);
        return [EB.getCurrentUsername()];
    } else {
        return prefs.friends
    }
}

The function EB.getCurrentUsername returns the currently active user, or false if no username has been set. If the user doesn't yet have any friends, create this list with a single member, themself.

Adding friends

Adding a friend is much like sending a message. First we verify that the account being added has a valid user record, and then we add this user to the list of friends.
function addFriend() {
    var friendToAdd = $("#friendToAdd").val();
    var prom = EB.Users.getUserRecordPromise(friendToAdd);

    prom.then(function() {
        var prefs = getPrefs();
        if (!prefs.friends) {
            EB.setPreference("friends",[EB.getCurrentUsername(),friendToAdd]);
        } else {
            prefs.friends.push(friendToAdd);
            EB.setPreference("friends",prefs.friends);
        }
        // handle GUI updating
    })
}                       
                        
EB.setPreference(key,value) is used to add or update the preference designated by key. It also handles updating the user record on the network for us.

Removing friends

What if we want to stop sharing with someone? All we have to do is remove them from our preferences.friends array.
function removeFriend() {
    var friendToRemove = $("#friendToRemove").val();
    var friends = getFriends();
    var index = friends.indexOf(friendToRemove);
    prefs.friends.splice(index,1);
    EB.setPreference("friends",prefs.friends);
}
                        
All we have to do is find and remove the supplied friend from our list of friends and then update the current user record with EB.setPreference

Sending Clips

Sending a clip has a bit of setup, but below we'll show how you can structure your sendClip() function.

 // sendClip
var usernames = getFriends();
var clipToSend = $("#sendInput")[0];
var clipEncodingPromise = FileFile.openBinaryFile(clipToSend);

clipEncodingPromise.then (function(encodedURI) {

    var type = "audio";
    var routes = usernames;
    var payload = {};
    payload.filename = clipToSend.files[0].name;

    var prom = EB.Users.usernamesToUserRecordsPromise(usernames);

    prom.then(function(userRecords) {
    var puff = EB.simpleBuildPuff(type, encodedURI, payload, routes, userRecords);
        EB.addPuffToSystem(puff);
        alert("Sent successfully!");
    })
    .catch(function(err) {
        alert(err);
        })
    })
})

The basic unit of content managed by EveryBit is called a puff. Continuing with sendClip, we build a puff containing the audio clip and send it off to the network. routes is used to ensure that the correct recipients download this content, and payload holds the content itself (as a base64-encoded data URI). Puffs act as the encrypted envelope that hold content to be sent over the network and as such no one except the sender and intended recipients can decrypt the envelope.

var clipEncodingPromise = PBFiles.openBinaryFile(clipToSend);

The purpose of this call is to convert the file to a data URI string so that we can pass it to the puff builder function as the content

We give the inner puff (the one containing our content) type "audio" so that when it is received, it will be rendered as specified by EB.M.Forum.addContentType above.

routes tells the system who this puff is for. It is always an array since EveryBit has support for sending encrypted content to multiple users at once.

All puffs must have a payload. For Songr's purposes we will use the payload to simply hold the filename of the clip

var prom = EB.Users.usernamesToUserRecordsPromise(usernames)

This takes the usernames of the friends we want to send to and returns the associated user records from the network.

prom.then(function(userRecords) {        
    var puff = EB.simpleBuildPuff(type, encodedURI, payload, routes, userRecords);
    EB.addPuffToSystem(puff);
    alert("Sent successfully!");
})
.catch(function(err) {
    alert(err);
})
                        

When the user records are returned from the network we now have access to our friends' public keys meaning we can encrypt and send our message. EB.simpleBuildPuff takes the content (in this case, our content is the data URI for the audio clip) we have and makes a puff with it. It then takes this puff and wraps it in the encrypted envelope puff. Hence the final structure of the puff that is sent over the network is shown below:

"{
  "username": "mrcutepuppy:4",
  "routes": [
    "sharewithmeplease"
  ],
  "previous": false,
  "version": "0.1.0",
  "payload": {
    "content": "U2FsdGVkX1/EFmQgqLSGx69Qtl......"
    "type": "encryptedpuff"
  },
  "keys": {
    "sharewithmeplease:1": "U2FsdGVkX18O4HAa8ermsaUVQp99KxM2fIZITFi9K5I="
  },
  "sig": "AN1rKvtephPB2NQHWTmEFqSmiimpgprq625BWEFVQ4JMvUAKF3YfSeaQsJYoKfB5vVupAyXUNWvPgCKBmg6a8FfRVEFgaTmgx"
}"
                        

This is an excerpt of a real encrypted puff from user "mrcutepuppy" to "sharewithmeplease". The numbers after their usernames represent the version of the user records used to encrypt the puff. The content has been shortened for the sake of readability however you can view the full version here

Getting Clips

Now that we can send audio clips, we just need to be able to view and play the ones sent to you.
function getSongsForMe() {
    var inbox = $("#inbox");
    var prom = EB.Data.getMorePrivatePuffs(EB.getCurrentUsername(),0,10);

    prom.then( function(report) {
        report.private_promise.then(function() {
            var myClips = EB.Data.getCurrentDecryptedLetters();
            inbox.html("");
            myClips.forEach(function(puff) {
                if(puff.payload.type == "audio") {
                    var content = EB.M.Forum.getProcessedPuffContent(puff);
                    inbox.append(content);
                }
            });
        });
    });
}
                        

Let's break down our API calls

var prom = EB.Data.getMorePrivatePuffs(EB.getCurrentUsername(),0,10)

The promise (prom) goes out on the network and gets us our 10 latest puffs. EB.getMorePrivatePuffs(username, offset, batchsize) returns a report object containing information based on what was returned from the network. We're keeping it simple for the tutorial by just always grabbing the 10 latest puffs.

var prom = EB.Data.getMorePrivatePuffs(EB.getCurrentUsername(),0,10)
    prom.then( function(report) {
        report.private_promise.then(function() {
            var myClips = EB.Data.getCurrentDecryptedLetters();
            inbox.html("");
            myClips.forEach(function(puff) {
                if(puff.payload.type == "audio") {
                    var content = EB.M.Forum.getProcessedPuffContent(puff);
                    inbox.append(content);
                }
            });
        });
    });
                        

This report contains a private_promise function that returns when the encrypted puffs we got off the network are finished decryption. We then call EB.Data.getCurrentDecryptedLetters() which returns an array of all locally decrypted puffs for the current user. We then loop through the decrypted puffs and display the ones of type "audio". EB.M.Forum.getProcessedPuffContent(puff) returns the value of the toHtml method of the EB.M.Forum.addContentType call in main.js.

Last steps

And that's practically it. We've built out all the logic needed for Songr to work. All that's left is to bind our functions to the GUI so that everything is user friendly.
$(document).ready(function() {
    $("#submitFile").bind("click", function(e) {
        e.preventDefault();
        var toUser = $("#sendTo").val();
        var fileToSend = $("#sendInput")[0].files[0];

        var sendContent = FileFile.openBinaryFile($("#sendInput")[0])

        if (!sendContent) {
            alert("you need to select a file to send");
            return false
        }

        sendContent.then(function (blob) {
            sendClip(blob, fileToSend.name);
        });

    });
    $("#getNewClips").bind("click",getSongsForMe);

    manageUserArea();

    getSongsForMe();

    if(EB.getCurrentUsername()) {
        showFriends();
    }
});