Commit 5c534abd authored by Vincent's avatar Vincent

Accessing our first bits of data!

Now we're getting started! In this commit, we fetch our first bit
of data: the name of the current user. To explain how that works,
I'll need to give you a crash course on Linked Data - but don't
worry, I'll limit myself only to what is absolutely necessary to
understand what's going on.

In traditional back-ends, data is usually stored in database
tables. When your back-end is a Solid Pod, however, data is stored
in _Documents_ living at a certain URL - you can think of these as
being similar to HTML documents living at a URL.

When a person logs in to our app, we receive a _WebID_. A WebID can
be used both to identify that user -since no other user shares that
URL-, and as a pointer to a Document with information relevant to
that user.

My WebID is `https://vincentt.inrupt.net/profile/card#me`. That
means that there is a Document at
`https://vincentt.inrupt.net/profile/card` that contains data
relevant to me.

The most important thing to understand when writing a Solid app is
how that data is represented. In a nutshell, a Document contains a
list of relationships between things. For example, the Document
referred to by my WebID contain the following relationships:

| <some person> | has name  | Vincent   |
| <some person> | works at  | Inrupt    |
| <some person> | job title | Developer |

(The common terminology is: every row contains a _Statement_, with
in the first column the Statement's _Subject_, in the second column
a _Predicate_, and the _Object_ in the third column.)

You might notice that the table above is a description of
`<some person>`: it's someone whose name is Vincent, who works at
Inrupt and is a developer. However, `<some person>` is not really
usable as a stable and unique identifier: for all we know, someone
else might have a Document elsewhere that uses the exact same
identifier. To solve this problem, Solid uses URLs as unique
identifiers: after all, the Document that describes the entries
already has a URL, and we can be sure that no other Document uses
the same one.

And like specific elements in an HTML document can be referred to
by appending their ID to the document's URL, we can give elements
we want to describe in a Document a unique identifier. As you might
have been able to guess, instead of `<some person>` we can use
`me` - hence the WebID
 `https://vincentt.inrupt.net/profile/card#me`!

So now we might consider my Document to look as follows:

| #me | has name  | Vincent   |
| #me | works at  | Inrupt    |
| #me | job title | Developer |

But there's one more thing to consider: interoperability. An
important tenet of Solid is being able to give multiple apps access
to the same data: if I enter my name at service A, I don't want to
have to re-enter it at service B. But if one service uses "name" to
refer to a person's full name, whereas the other uses it to refer
to a person's last name only, that would nip interoperbility in
the bud.

What's needed here is unique terms that have an agreed-upon
definition. And just like we can have a Document describing me, we
could also make Documents describing a term. And in fact, many
people have done exactly that, for many different terms you might
want to use. These Documents are called _Vocabularies_, and there's
one for things you might want to put on a business card at
http://www.w3.org/2006/vcard/ns — the _vCard_ Vocabulary. It
contains Statements along the lines of:

(As a shorthand for `http://www.w3.org/2006/vcard/ns#role`, we
will use `vcard:role`.)

So now we can use `vcard:role`, and be relatively confident that
every other app using it will use it in the way described at that
URL. We can combine terms from different Vocabularies, e.g. the
FOAF ("Friend of a friend") vocabulary has a term to refer to a
person's name at `http://xmlns.com/foaf/0.1/name`. My Document
could thus look something like this:

| #me | foaf:name               | Vincent   |
| #me | vcard:organization-name | Inrupt    |
| #me | vcard:role              | Developer |

Everything that needs to be uniquely defined has a URL, with some
_Literal_ values for the rest ("Vincent", "Inrupt", "Developer").
You could imagine "Inrupt" to be replaced by a URL as well,
pointing to a Document describing the organisation itself - but for
the intents and purposes of this tutorial, we will leave it at this.
(It might give you some insight into why all this is referred to as
"Linked Data" though!)

A Document can describe multiple things. As an example, my Document
could also have an entry for my address, and then link that to me:

| #me       | foaf:name               | Vincent             |
| #me       | vcard:organization-name | Inrupt              |
| #me       | vcard:role              | Developer           |
| #me       | vcard:hasAddress        | #address1           |
| #address1 | vcard:street-address    | 155 Country Lane    |
| #address1 | vcard:region            | Cottingshire County |

Phew! That should cover about all the Linked Data theory you should
know to start working with Solid. So now let's see what's actually
happening in this commit.

The only user-visible change in this commit is that their name will
be shown when they are logged in, and a foaf:name is present in
their profile Document. So how is this done?

The magic is in the newly added `fetchProfile` function. This
function does two things:

First, it requests the user's WebID from `solid-auth-client`. When
the user has logged in using the authentication mechanism we added
in an earlier commit, this will return their WebID, i.e. the URL to
a Document container a representation of the user.

Then, with the WebID in hand, it uses a library called _Tripledoc_
to retrieve that document's content. Then, `document.getSubject`
returns a representation of all the Statements in that document
that have the user's WebID as the Subject - so in the example table
above, the rows with `#me` in the first column rather than
`#address1`.

`useProfile` is then called in the `<Dashboard>` component.
Although `useProfile` provides access to all Statements about the
user in their profile, we're currently only really interested in
their name. We're going to use the `name` predicate of FOAF for
this, so we're looking for a Statement like:

| #me | http://xmlns.com/foaf/0.1/name | Vincent |

Since `useProfile` returned all Statements with `#me` as the
Subject, the Dashboard has access to it through `profile`. We can
access the name by calling:

    const name = profile.getString('http://xmlns.com/foaf/0.1/name');

That returns a the user's name, if set to a literal string value,
rather than a URL that points to a different Document.

There are a number of Vocabularies that define terms used in many
types of apps. To save you from having to copy-paste the URL of a
Vocabulary time and time again, the `rdf-namespaces` package
includes some useful helper objects for common Vocabularies —
such as FOAF. Thus, the above can be shortened to:

    import { foaf } from 'rdf-namespaces';
    const name = profile.getString(foaf.name);

Finally, one disadvantage of Solid, as a developer, is that there
are no guarantees about the data you want to access: you cannot be
sure that a piece of data exists. The user might not have provided
their name, or used a different Vocabulary to represent it. Thus,
we should take into account that `profile.getString` might return
`null`.
parent f42f8ae6
This diff is collapsed.
......@@ -8,12 +8,16 @@
"@types/node": "12.6.6",
"@types/react": "16.8.23",
"@types/react-dom": "16.8.4",
"@types/solid-auth-client": "^2.3.0",
"@types/solid__react": "^1.6.1",
"bulma": "^0.7.5",
"node-sass": "^4.12.0",
"rdf-namespaces": "^1.5.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"solid-auth-client": "^2.3.0",
"tripledoc": "^2.2.0",
"typescript": "3.5.3"
},
"scripts": {
......@@ -36,5 +40,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"devDependencies": {}
}
import React from 'react';
import { LogoutButton } from '@solid/react';
import { foaf } from 'rdf-namespaces';
import { useProfile } from '../hooks/useProfile';
export const Dashboard: React.FC = () => {
const profile = useProfile();
const name = (profile) ? profile.getString(foaf.name) : null;
const greeting = (name)
? `Hi ${name}`
: 'Hi';
return <>
<section className="section">You are logged in! You will be able to do things here.</section>
<section className="section">
{greeting}
</section>
<footer className="footer has-text-right">
<LogoutButton className="button"/>
</footer>
......
import React from 'react';
import { TripleSubject } from 'tripledoc';
import { fetchProfile } from '../services/fetchProfile';
export function useProfile() {
const [profile, setProfile] = React.useState<TripleSubject>();
React.useEffect(() => {
fetchProfile().then((fetchedProfile) => {
if (fetchedProfile === null) {
return;
}
setProfile(fetchedProfile);
});
}, []);
return profile;
}
import solidAuth from 'solid-auth-client';
import { fetchDocument } from 'tripledoc';
export async function fetchProfile () {
const currentSession = await solidAuth.currentSession();
if (!currentSession) {
return null;
}
const webIdDoc = await fetchDocument(currentSession.webId);
const profile = webIdDoc.getSubject(currentSession.webId);
return profile;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment