Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upDat Library and User profiles
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
- define where to find data,
- define how data relates to identity, and
- 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:
If the user wants to create a new profile, they can.
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_nameString. The name identifying the user. May be any valid string. -
bioString. A short biography for the user. -
avatarString. The pathname of the avatar image. (It must be stored in the profile archive.) -
datasetsArray. The data archives published by the user. Each entry is an object with:-
titleString. The title of the archive. -
typeString. A single URL which identifies the content of the archive. -
urlString. 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:
-
typeString. 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 instanceDatUserProfile 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 propsExamples
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) {
// ...
}
