Skip to content

3Box JavaScript SDK: Manage user identity, profiles, storage, messaging

License

Notifications You must be signed in to change notification settings

mohamedaboelmagd/3box-js

 
 

Repository files navigation

CircleCI Discord npm npm Codecov Twitter Follow Greenkeeper badge

Install | Usage | Example | Data Standards | API Docs

3box-js

This is a library which allows you to set, get, and remove private and public data associated with an ethereum account. It can be used to store identity data, user settings, etc. by dapps that use a web3 enabled browser. The data will be retrievable as long as the user has access to the private key for the used ethereum account. The data is encrypted and can not be read by any third party that the user hasn't authorized. There is one shared space for data which all authorized dapps access by default, then there are spaces which dapps have to request explicit consent to access.

Getting Started

Installation

Install 3box in your npm project:

$ npm install 3box

Usage

Import 3Box into your project

Import the 3box module

const Box = require("3box");

Import using the dist build in your html code

<script type="text/javascript" src="../dist/3box.js"></script>

Or optionally by loading remote copy from unpkg CDN.

<!-- The most recent version  -->
<script src="https://unpkg.com/3box/dist/3box.js"></script>
<!-- The most recent minified version  -->
<script src="https://unpkg.com/3box/dist/3box.min.js"></script>
<!-- Load specific versions by specifying the version as follows -->
<script src="https://unpkg.com/3box@<version>/dist/3box.js"></script>

Profiles API

Get the existing public profile of an address (or DID)

3Box allows users to create a public profile for their Ethereum address. In your dapp you might have multiple ethereum addresses that you would like to display a name, image, and other basic social metadata for. The getProfile method allows you to fetch the public profile of any ethereum address (if it has one). This is a static method so you can call it directly from the Box object.

const profile = await Box.getProfile("0x12345abcde");
console.log(profile);

Update (get, set, remove) public and private profile data

3Box allows applications to create, read, update, and delete public and private data stored in a user's 3Box. To enable this functionality, applications must first open the user's 3Box by calling the openBox method. This method prompts the user to authenticate (sign-in) to your dapp and returns a promise with a threeBox instance. You can only update (set, get, remove) data for users that have authenticated to and are currently interacting with your dapp. Below ethereumProvider refers to the object that you would get from web3.currentProvider, or window.ethereum.

1. Authenticate users to begin new 3Box session

Calling the openBox method will open a new 3Box session. If the user's ethereum address already has a 3Box account, your application will gain access to it. If the user does not have an existing 3Box account, this method will automatically create one for them in the background.

const box = await Box.openBox("0x12345abcde", ethereumProvider);

2. Sync user's available 3Box data from the network

When you first open the box in your dapp all data might not be synced from the network yet. You should therefore add a listener using the onSyncDone method. This will allow you to know when all the user's data is available to you. We advise against setting any data before this sync has happened. However, reading data before the sync is complete is fine and encouraged - just remember to check for updates once this callback is fired!

box.onSyncDone(yourCallbackFunction);

3. Interact with 3Box profile data

You can now use the box instance object to interact with public and private data stored in the user's profile. In both the public and the private data store you use a key to set a value.

// use the public profile
// get
const nickname = await box.public.get("name");
console.log(nickname);
// set
await box.public.set("name", "oed");
// remove
await box.public.remove("name");

// use the private store
// get
const email = await box.private.get("email");
console.log(email);
// set
await box.private.set("email", "[email protected]");
// remove
await box.private.remove("email");
Set multiple fields at once:
const fields = ["name", "website", "employer"];
const values = ["Jon Schwartz", "openworklabs.com", "Open Work Labs"];

await box.public.setMultiple(fields, values);

const privateFields = ["age", "coinBalance"];
const privateValues = ["xxx", "yyy"];

await box.private.setMultiple(privateFields, privateValues);

Spaces API (Storage)

Open a space

A space is a named section of a users 3Box. Each space has both a public and a private store, and for every space you open the user has to grant explicit consent to view that space. This means that if your dapp uses a space that no other dapp uses, only your dapp is allowed to update the data and read the private store of that particular space. To open a space called narwhal you simply call:

const space = await box.openSpace("narwhal");

Get, set, and remove space data

Interacting with data in a space is done in the same way as interacting with box.public and box.private (see here). For example:

const config = await space.private.get("dapp-config");

Threads API (Messaging)

Add message threads to your app

Threads are a shared datastore that enable decentralized communication between users, by allowing one or more users to post messages in a sequence. This functionality is great for adding commenting, chat, messaging, feed, and stream features to your application. Threads are saved within a space and users that join a thread (with the same name, in the same space, and same moderation configs) will be able to communicate in that thread.

For the fully detailed spec, view the documentation.

Viewing a Thread

You can get all posts made in a thread without opening a space. This is great for allowing visitors of your site view comments made by other users. This is achieved by calling the getThread method on the Box object. A thread can be referenced by all its configuration options or by its address.

const posts = await Box.getThread(
  spaceName,
  threadName,
  firstModerator,
  membersThread
);
console.log(posts);
const posts = await Box.getThreadByAddress(threadAddress);
console.log(posts);

However if applications want to add interactivity to the thread, such as allowing the user to post in a thread or follow updates in a thread, you will need to open their space to enable additional functionality.

Interacting with a Thread

1. Joining a thread

To post in a thread, a user must first join the thread. This will implicitly use the moderation options where the current user is the firstModerator and members is false.

const thread = await space.joinThread("myThread");

A thread can also be given the moderation options when joining. You can pass firstModerator, a 3ID of the first moderator, and a members boolean which indicates if it is a members thread or not. Moderators can add other moderators, add members, and delete any posts in the thread. Members can post in member only threads.

const thread = await space.joinThread("myThread", {
  firstModerator: "some3ID",
  members: true,
});

Lastly a thread can be joined by its address.

const thread = await space.joinThreadByAddress(
  "/orbitdb/zdpuAp5QpBKR4BBVTvqe3KXVcNgo4z8Rkp9C5eK38iuEZj3jq/3box.thread.testSpace.testThread"
);
2. Posting to a thread

This allows the user to add a message to the thread. The author of the message will be the user's 3Box DID. When a user posts in a thread, they are automatically subscribed to the thread and it is saved in the space used by the application under the key thread-threadName.

await thread.post("hello world");
3. Getting all posts in a thread

This allows applications to get the posts in a thread.

const posts = await thread.getPosts();
console.log(posts);
4. Listening for updates in thread

This allows applications to listen for new posts in the thread, and perform an action when this occurs, such as adding the new message to the application's UI.

thread.onUpdate(myCallbackFunction);
5. Handling moderation and capabilities

Add a moderator and list all existing moderators

await thread.addModerator("some3ID");

const mods = await thread.listModerators();

Add a member and list all existing members, if a members only thread

await thread.addMember("some3ID");

const members = await thread.listMembers();

Listen for when there has been moderators or member added.

thread.onNewCapabilities(myCallbackFunction);

Example Application

You can quickly run and interact with some code by looking at the files in the /example folder. You run the example with the following command:

$ npm run example:start

This runs a simple server at http://localhost:3000/ that serves the static example/index.html file. This allows it easily interact with metamask. You can edit the example/index.html file to try differnt code.

Build

Optimize build for read-only 3Box API

If you only want to fetch profile data from 3Box's profile APIs you can optimize by importing only those functions or the API specific dist file. Since this includes minimal dependencies, file size is ~ 80kb vs 4+mb for the full build.

const {
  profileGraphQL,
  getProfile,
  getProfiles,
  getVerifiedAccounts,
} = require("3box/lib/api");
<script src="https://unpkg.com/3box/dist/3box.api.min.js"></script>

Resolving build size issues and out of memory errors

Some platforms, tooling, or configs have caused the build process to throw out of memory errors. This is a combination of the size of our library (plus dependencies) and the specific configs you have for your build. It could be things like tooling running on dependencies and not just your source or dependencies be recursively resolved. You can attempt to build the library anyways by adding the follow environment variable to increase memory for the node process.

NODE_OPTIONS=--max_old_space_size=4096 npm run build

Data Standards

Dapps can store data about users that relate to only their dapp. However we encurage dapps to share data between them for a richer web3 experience. Therefore we have created Key Conventions in order to facilitate this. Feel free to make a PR to this file to explain to the community how you use 3Box!

Validate claims

Use the idUtils module to validate claims. See the did-jwt library for more details.

const { idUtils } = require('3box')

const claim = 'eyJ0eX...'
idUtils.verifyClaim(claim)
  .then(valid => console.info('details:', valid)
  .catch(err => console.error('claim verification failed:', err)

API Documentation

Box

Kind: global class

new Box()

Please use the openBox method to instantiate a 3Box

box.public

Kind: instance property of Box
Properties

Name Type Description
public KeyValueStore access the profile store of the users 3Box

box.private

Kind: instance property of Box
Properties

Name Type Description
private KeyValueStore access the private store of the users 3Box

box.verified

Kind: instance property of Box
Properties

Name Type Description
verified Verified check and create verifications

box.spaces

Kind: instance property of Box
Properties

Name Type Description
spaces Object an object containing all open spaces indexed by their name.

box.DID

Kind: instance property of Box
Properties

Name Type Description
DID String the DID of the user

box.openSpace(name, opts) ⇒ Space

Opens the space with the given name in the users 3Box

Kind: instance method of Box
Returns: Space - the Space instance for the given space name

Param Type Description
name String The name of the space
opts Object Optional parameters
opts.consentCallback function A function that will be called when the user has consented to opening the box
opts.onSyncDone function A function that will be called when the space has finished syncing with the pinning node

box.onSyncDone(syncDone)

Sets the callback function that will be called once when the db is fully synced.

Kind: instance method of Box

Param Type Description
syncDone function The function that will be called

box.linkAddress([link])

Creates a proof that links an ethereum address to the 3Box account of the user. If given proof, it will simply be added to the root store.

Kind: instance method of Box

Param Type Default Description
[link] Object Optional link object with type or proof
[link.type] String 'ethereum-eoa' The type of link (default 'ethereum')
[link.proof] Object Proof object, should follow spec

box.removeAddressLink(address)

Remove given address link, returns true if successful

Kind: instance method of Box

Param Type Description
address String address that is linked

box.isAddressLinked([query])

Checks if there is a proof that links an external account to the 3Box account of the user. If not params given and any link exists, returns true

Kind: instance method of Box

Param Type Description
[query] Object Optional object with address and/or type.
[query.type] String Does the given type of link exist
[query.address] String Is the given adressed linked

box.listAddressLinks() ⇒ Array

Lists address links associated with this 3Box

Kind: instance method of Box
Returns: Array - An array of link objects

box.logout()

Closes the 3box instance and clears local cache. If you call this, users will need to sign a consent message to log in the next time you call openBox.

Kind: instance method of Box

Box.idUtils

A module to verify & validate claims

Kind: static property of Box

idUtils.verifyClaim ⇒ Object

Verify a claim and return its content. See https://github.com/uport-project/did-jwt/ for more details.

Kind: static property of idUtils
Returns: Object - The validated claim

Param Type Description
claim String
opts Object Optional parameters
opts.audience string The DID of the JWT's audience

idUtils.isSupportedDID(did) ⇒ * | boolean

Check whether a string is a muport did or not

Kind: static method of idUtils
Returns: * | boolean - Whether the did is a supported did or not

Param Type Description
did String A string containing a user did

idUtils.isClaim(claim, opts) ⇒ Promise.<boolean>

Check whether a string is a valid claim or not

Kind: static method of idUtils
Returns: Promise.<boolean> - whether the parameter is an actual claim

Param Type Description
claim String
opts Object Optional parameters
opts.audience string The DID of the audience of the JWT

Box.getProfile(address, opts) ⇒ Object

Get the public profile of a given address

Kind: static method of Box
Returns: Object - a json object with the profile for the given address

Param Type Description
address String An ethereum address
opts Object Optional parameters
opts.blocklist function A function that takes an address and returns true if the user has been blocked
opts.metadata String flag to retrieve metadata
opts.addressServer String URL of the Address Server
opts.ipfs Object A js-ipfs ipfs object
opts.useCacheService Boolean Use 3Box API and Cache Service to fetch profile instead of OrbitDB. Default true.
opts.profileServer String URL of Profile API server

Box.getProfiles(address, opts) ⇒ Object

Get a list of public profiles for given addresses. This relies on 3Box profile API.

Kind: static method of Box
Returns: Object - a json object with each key an address and value the profile

Param Type Description
address Array An array of ethereum addresses
opts Object Optional parameters
opts.profileServer String URL of Profile API server

Box.getSpace(address, name, opts) ⇒ Object

Get the public data in a space of a given address with the given name

Kind: static method of Box
Returns: Object - a json object with the public space data

Param Type Description
address String An ethereum address
name String A space name
opts Object Optional parameters
opts.blocklist function A function that takes an address and returns true if the user has been blocked
opts.metadata String flag to retrieve metadata
opts.profileServer String URL of Profile API server

Box.getThread(space, name, firstModerator, members, opts) ⇒ Array.<Object>

Get all posts that are made to a thread.

Kind: static method of Box
Returns: Array.<Object> - An array of posts

Param Type Description
space String The name of the space the thread is in
name String The name of the thread
firstModerator String The DID (or ethereum address) of the first moderator
members Boolean True if only members are allowed to post
opts Object Optional parameters
opts.profileServer String URL of Profile API server

Box.getThreadByAddress(address, opts) ⇒ Array.<Object>

Get all posts that are made to a thread.

Kind: static method of Box
Returns: Array.<Object> - An array of posts

Param Type Description
address String The orbitdb-address of the thread
opts Object Optional parameters
opts.profileServer String URL of Profile API server

Box.getConfig(address, opts) ⇒ Array.<Object>

Get the configuration of a users 3Box

Kind: static method of Box
Returns: Array.<Object> - An array of posts

Param Type Description
address String The ethereum address
opts Object Optional parameters
opts.profileServer String URL of Profile API server

Box.listSpaces(address, opts) ⇒ Object

Get the names of all spaces a user has

Kind: static method of Box
Returns: Object - an array with all spaces as strings

Param Type Description
address String An ethereum address
opts Object Optional parameters
opts.profileServer String URL of Profile API server

Box.profileGraphQL(query, opts) ⇒ Object

GraphQL for 3Box profile API

Kind: static method of Box
Returns: Object - a json object with each key an address and value the profile

Param Type Description
query Object A graphQL query object.
opts Object Optional parameters
opts.graphqlServer String URL of graphQL 3Box profile service

Box.getVerifiedAccounts(profile) ⇒ Object

Verifies the proofs of social accounts that is present in the profile.

Kind: static method of Box
Returns: Object - An object containing the accounts that have been verified

Param Type Description
profile Object A user profile object, received from the getProfile function

Box.openBox(address, ethereumProvider, opts) ⇒ Box

Opens the 3Box associated with the given address

Kind: static method of Box
Returns: Box - the 3Box instance for the given address

Param Type Description
address String An ethereum address
ethereumProvider ethereumProvider An ethereum provider
opts Object Optional parameters
opts.consentCallback function A function that will be called when the user has consented to opening the box
opts.pinningNode String A string with an ipfs multi-address to a 3box pinning node
opts.ipfs Object A js-ipfs ipfs object
opts.addressServer String URL of the Address Server
opts.contentSignature String A signature, provided by a client of 3box using the private keys associated with the given address, of the 3box consent message

Box.isLoggedIn(address) ⇒ Boolean

Check if the given address is logged in

Kind: static method of Box
Returns: Boolean - true if the user is logged in

Param Type Description
address String An ethereum address

KeyValueStore

Kind: global class

new KeyValueStore()

Please use box.public or box.private to get the instance of this class

keyValueStore.get(key, opts) ⇒ String | Object

Get the value and optionally metadata of the given key

Kind: instance method of KeyValueStore
Returns: String | Object - the value associated with the key, undefined if there's no such key

Param Type Description
key String the key
opts Object optional parameters
opts.metadata Boolean return both value and metadata

keyValueStore.getMetadata(key) ⇒ Metadata

Get metadata for for a given key

Kind: instance method of KeyValueStore
Returns: Metadata - Metadata for the key, undefined if there's no such key

Param Type Description
key String the key

keyValueStore.set(key, value) ⇒ Boolean

Set a value for the given key

Kind: instance method of KeyValueStore
Returns: Boolean - true if successful

Param Type Description
key String the key
value String the value

keyValueStore.setMultiple(keys, values) ⇒ Boolean

Set multiple values for multiple keys

Kind: instance method of KeyValueStore
Returns: Boolean - true if successful, throw error if not

Param Type Description
keys Array.<String> the keys
values Array.<String> the values

keyValueStore.remove(key) ⇒ Boolean

Remove the value for the given key

Kind: instance method of KeyValueStore
Returns: Boolean - true if successful

Param Type Description
key String the key

keyValueStore.all(opts) ⇒ Array.<(String|{value: String, timestamp: Number})>

Get all values and optionally metadata

Kind: instance method of KeyValueStore
Returns: Array.<(String|{value: String, timestamp: Number})> - the values

Param Type Description
opts Object optional parameters
opts.metadata Boolean return both values and metadata

keyValueStore.log() ⇒ Array.<Object>

Returns array of underlying log entries. In linearized order according to their Lamport clocks. Useful for generating a complete history of all operations on store.

Kind: instance method of KeyValueStore
Returns: Array.<Object> - Array of ordered log entry objects
Example

const log = store.log;
const entry = log[0];
console.log(entry);
// { op: 'PUT', key: 'Name', value: 'Botbot', timeStamp: '1538575416068' }

Space

Kind: global class

new Space()

Please use box.openSpace to get the instance of this class

space.public

Kind: instance property of Space
Properties

Name Type Description
public KeyValueStore access the profile store of the space

space.private

Kind: instance property of Space
Properties

Name Type Description
private KeyValueStore access the private store of the space

space.DID

Kind: instance property of Space
Properties

Name Type Description
DID String the did of the user in this space

space.joinThread(name, opts) ⇒ Thread

Join a thread. Use this to start receiving updates from, and to post in threads

Kind: instance method of Space
Returns: Thread - An instance of the thread class for the joined thread

Param Type Description
name String The name of the thread
opts Object Optional parameters
opts.firstModerator String DID of first moderator of a thread, by default, user is first moderator
opts.members Boolean join a members only thread, which only members can post in, defaults to open thread
opts.noAutoSub Boolean Disable auto subscription to the thread when posting to it (default false)

space.joinThreadByAddress(address, opts) ⇒ Thread

Join a thread by full thread address. Use this to start receiving updates from, and to post in threads

Kind: instance method of Space
Returns: Thread - An instance of the thread class for the joined thread

Param Type Description
address String The full address of the thread
opts Object Optional parameters
opts.noAutoSub Boolean Disable auto subscription to the thread when posting to it (default false)

space.subscribeThread(address, config)

Subscribe to the given thread, if not already subscribed

Kind: instance method of Space

Param Type Description
address String The address of the thread
config Object configuration and thread meta data
opts.name String Name of thread
opts.firstModerator String DID of the first moderator
opts.members String Boolean string, true if a members only thread

space.unsubscribeThread(address)

Unsubscribe from the given thread, if subscribed

Kind: instance method of Space

Param Type Description
address String The address of the thread

space.subscribedThreads() ⇒ Array.<Objects>

Get a list of all the threads subscribed to in this space

Kind: instance method of Space
Returns: Array.<Objects> - A list of thread objects as { address, firstModerator, members, name}

Thread

Kind: global class

new Thread()

Please use space.joinThread to get the instance of this class

thread.post(message) ⇒ String

Post a message to the thread

Kind: instance method of Thread
Returns: String - The postId of the new post

Param Type Description
message Object The message

thread.addModerator(id)

Add a moderator to this thread, throws error is user can not add a moderator

Kind: instance method of Thread

Param Type Description
id String Moderator Id

thread.listModerators() ⇒ Array.<String>

List moderators

Kind: instance method of Thread
Returns: Array.<String> - Array of moderator DIDs

thread.addMember(id)

Add a member to this thread, throws if user can not add member, throw is not member thread

Kind: instance method of Thread

Param Type Description
id String Member Id

thread.listMembers() ⇒ Array.<String>

List members, throws if not member thread

Kind: instance method of Thread
Returns: Array.<String> - Array of member DIDs

thread.deletePost(id)

Delete post

Kind: instance method of Thread

Param Type Description
id String Moderator Id

thread.getPosts(opts) ⇒ Array.<Object>

Returns an array of posts, based on the options. If hash not found when passing gt, gte, lt, or lte, the iterator will return all items (respecting limit and reverse).

Kind: instance method of Thread
Returns: Array.<Object> - true if successful

Param Type Description
opts Object Optional parameters
opts.gt String Greater than, takes an postId
opts.gte String Greater than or equal to, takes an postId
opts.lt String Less than, takes an postId
opts.lte String Less than or equal to, takes an postId
opts.limit Integer Limiting the number of entries in result, defaults to -1 (no limit)
opts.reverse Boolean If set to true will result in reversing the result

thread.onUpdate(updateFn)

Register a function to be called after new updates have been received from the network or locally.

Kind: instance method of Thread

Param Type Description
updateFn function The function that will get called

thread.onNewCapabilities(updateFn)

Register a function to be called for every new capability that is added to the thread access controller. This inlcudes when a moderator or member is added. The function takes one parameter, which is the capabilities obj, or you can call listModerator / listMembers again instead.

Kind: instance method of Thread

Param Type Description
updateFn function The function that will get called

Verified

Kind: global class

new Verified()

Please use box.verified to get the instance of this class

verified.DID() ⇒ String

Returns the verified DID of the user

Kind: instance method of Verified
Returns: String - The DID of the user

verified.github() ⇒ Object

Verifies that the user has a valid github account Throws an error otherwise.

Kind: instance method of Verified
Returns: Object - Object containing username, and proof

verified.addGithub(gistUrl) ⇒ Object

Adds a github verification to the users profile Throws an error if the verification fails.

Kind: instance method of Verified
Returns: Object - Object containing username, and proof

Param Type Description
gistUrl Object URL of the proof

verified.twitter() ⇒ Object

Verifies that the user has a valid twitter account Throws an error otherwise.

Kind: instance method of Verified
Returns: Object - Object containing username, proof, and the verifier

verified.addTwitter(claim) ⇒ Object

Adds a twitter verification to the users profile Throws an error if the verification fails.

Kind: instance method of Verified
Returns: Object - Object containing username, proof, and the verifier

Param Type Description
claim String A did-JWT claim ownership of a twitter username

verified.email() ⇒ Object

Verifies that the user has a verified email account Throws an error otherwise.

Kind: instance method of Verified
Returns: Object - Object containing username, proof, and the verifier

verified.addEmail(claim) ⇒ Object

Adds an email verification to the users profile Throws an error if the verification fails.

Kind: instance method of Verified
Returns: Object - Object containing username, proof, and the verifier

Param Type Description
claim String A did-JWT claim ownership of an email username

test commit 3

About

3Box JavaScript SDK: Manage user identity, profiles, storage, messaging

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 97.0%
  • HTML 3.0%