Dat Library and User profiles

Paul Frazee edited this page Jul 13, 2017 · 29 revisions

One of Beaker's goals is to empower users to control their personal data. In Beaker's peer-to-peer applications model, services don't manage data, so the browser itself must provide data management tools. It must provide users with tools which

  1. define where to find data,
  2. define how data relates to identity, and
  3. define which apps are allowed to access data.

Identity on the Web is more than just a username and avatar; it encompasses all of the content and data linked to a given identity. Users frequently make security and social decisions on the basis of "which account" they are using. Therefore, we believe identity should be at the center of data storage in Beaker.

This spec defines a "Library" and "user profiles" system built on Dat archives. This spec also defines APIs and UX flows for accessing datasets from applications.

Requirements

  • Users must be able to create many identities.
  • Users must be able to share their datasets by sharing an identity.
  • Users must always know which identity their datasets are associated with.
  • Users must be able to access their identity and datasets from multiple applications.

We also set the following requirements, which apply to all Beaker specs:

  • Decision prompts should be clearly connected to, and contextualized by, an activity flow.
  • Users should never be prompted with information that can't be explained, or which they can't act on.

Proposal

This spec creates a high level organization around Dat archives. This system is called the "Library." The Library helps the user find their data, share their content, and control their apps.

The Library separates archives into two "categories." They are:

  • Profile archive. Represents a user identity. Contains a bucket of data archives.
  • Data archive. Stores data generated by applications. Is always attached to a profile archive.

Organization

When the user is looking for a file, they will first choose a profile, then a data archive, then the file(s).

An example "path" to a status update might look like this:

Main Profile > Social Feed > /status/5.json

And here, an example "path" to a work document might look like this:

Blue Link Labs Profile > Documents > /business/2017-receipts.csv

Signin flow

Applications gain access to profiles and data archives by "signing in."

In the signin flow, we give the user the option to choose from one of their profile archives. This is presented by at beaker://signin. Signin is triggered by the application, but only after a user-click.

Here's a simple mockup of the signin screen:

profile selection

If the user wants to create a new profile, they can.

profile creation

Once the user signs into a profile for an application, the browser grants the app full read/write access to the profile's data archives.

Profile archives

Profile archives provide information about the user. They also act as the primary ID of the user, and provide the user with a keypair for signing and encrypting information.

Applications are not allowed to write to the profile archive. Applications create separate data archives, and attach them to the profile archive.

Profile information (/profile.json)

The /profile.json file contains profile information. The file includes the following data:

  • display_name String. The name identifying the user. May be any valid string.
  • bio String. A short biography for the user.
  • avatar String. The pathname of the avatar image. (It must be stored in the profile archive.)
  • datasets Array. The data archives published by the user. Each entry is an object with:
    • title String. The title of the archive.
    • type String. A single URL which identifies the content of the archive.
    • url String. The URL of the archive.

Example profile.json:

{
  "display_name": "Bob Roberts",
  "bio": "Just another user.",
  "avatar": "/bob.png",
  "datasets": [{
    "title": "Social Feed",
    "type": "https://example.com/social-feed",
    "url": "dat://.../"
  }]
}

Data archives

Data archives are created by applications. They are given specific types, which are identified by a URL. They are also given a title, which can be changed by the user.

A data archive can only have one type. The structure of a data archive is decided by the application, but it should adhere to any specifications defined by the type URL. The type is analogous to a file extension or mime type.

/dat.json Data manifest fields

A data archive adds one attribute to the standard /dat.json schema:

  • type String. A single URL which identifies the content of the archive.

Example dat.json of a data archive:

{
  "title": "Social Feed",
  "type": "https://example.com/social-feed"
}

Applications

Applications can use the UserSession API to create sessions with the user's profiles. Sessions grant the app temporary access to the profile's data archives, for both reading and writing.

Applications must be served over dat:// to access these APIs.

/dat.json Application manifest fields

Applications that want to access the datasets on user profiles need to specify which datasets it will access in their /dat.json manifest. The user will be presented with the list of datasets during signin. Upon signin, if a dataset of the given type does not exist yet, it will be created with the title given in the app manifest.

The application adds the following attributes:

  • datasets. Array. The values are objects with the following values:
    • type. String. A URL identifying the type of the dataset.
    • title. String. A suggested title for the dataset on creation.

Example dat.json of an application archive:

{
  "title": "My social app",
  "description": "I consume and publish status updates!",
  "datasets": [{
    "type": "https://example.com/statusfeed",
    "title": "Status feed"
  }]
}

The datasets can be accessed using DatUserProfile#getDataset().

UserSession API

var session = await UserSession.fetch() // get the session
await session.requestSignin() // request an active session
await session.signout() // end the session
session.isActive // bool, is the user signed in?
session.profile // a DatUserProfile instance

DatUserProfile API

The DatUserProfile inherits all methods and properties from DatArchive.

var profile = new DatUserProfile(url) // new instance
var profileJson = await profile.getProfileJson() // get profile.json values
var archive = await profile.getDataset(type) // get one of the user's datasets, by type
// profile also inherits all DatArchive methods and props

Examples

Example login / logout

// globals
var sess // the user session
var profileJson // the user's profile data
var loginErr // error during login
setup()

async function setup () {
  // fetch a session object
  // this may already be setup from a previous run
  // or it may be a totally new session
  // sess.isActive tells us which it is
  sess = await UserSession.fetch()
  updateUI()
}

function updateUI() {
  // main render function
  // we call this function any time the state changes
  yo.update(document.querySelector('main'), yo`<main>
    <header>
      ${renderProfile()}
      ${renderLogin()}
    </header>
  </main>`)
}

function renderLogin () {
  // the UI for 'Log in' / 'Change profile'
  // renders any login error that might exist
  // then renders a button to login or change profile
  // (notice that onLogin works for both usecases)
  return yo`<div class="login">
    ${loginErr ? yo`<span class="error">${loginErr.toString()}</span>` : ''}
    <button onclick=${onLogin}>${sess.isActive ? 'Change profile' : 'Log in'}</button>
  </div>`
}

function renderProfile () {
  // the ui for the user's profile
  // renders nothing if not signed in
  // renders the configured profile info if active
  if (!sess.isActive) {
    return ''
  }

  // render profile info
  return yo`<div class="profile">
    <img src=${profileJson.avatar}>
    <span>${profileJson.display_name}</span>
  </div>`
}

async function onLogin () {
  // login event handler
  // attempts a login
  // saves the error on failure
  // then renders
  try {
    loginErr = null
    await sess.requestSignin()
    profileJson = await sess.profile.getProfileJson()
  } catch (e) {
    loginErr = e
  }
  updateUI()
}

Example of publishing user data

In the application's dat.json manifest:

{
  ...
  "datasets": [{
    "type": "https://example.com/statusfeed",
    "title": "Status feed"
  }],
  ...
}

In the application's code:

try {
  var statusFeed = await sess.getDataset('https://example.com/statusfeed')
  await statusFeed.writeFile('/${Date.now()}.txt', 'Just signed in!')
} catch (err) {
  // ...
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.