Skip to content

Commit

Permalink
Import current drafts of "Writing Solid apps" (#2)
Browse files Browse the repository at this point in the history
* Import current drafts of "Writing Solid apps"

* writing-apps Added the notification example first draft
  • Loading branch information
Vinnl authored and jairo-campos-JD committed Sep 25, 2019
1 parent f25126d commit 39d17ae
Show file tree
Hide file tree
Showing 7 changed files with 462 additions and 0 deletions.
77 changes: 77 additions & 0 deletions _posts/learn/build-on-solid/common-patterns/1-notification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
layout: post
title: Notification in Solid
tags: [build], [pattern]
categories: [Build on Solid], [Common patterns]
permalink: Build-on-Solid/common-patterns/1-notification
exclude: true
---

# Notification in Solid apps

When developing social apps, where users interact asynchronously, or whenever an app wants to let you know that something relevant happened, notifications come in handy. Let's see how they are used in Solid.

## What you'll need for this tutorial

### Interacting entities

- We'll consider the online exchanges between [Cleopatra](https://cleopatra.solid.community) and [Julius Caesar](https://jcaesar.solid.community), both known for their activity on social media (among other things). In this case, we're not interested in Caesar as an imperarot, but rather as a successful book author: 'De Bello Gallico' is about to be out, and Caesar is having a release party for his memoirs. He wants to invite Cleopatra as a fellow elite member of the Roman society.
- If you have one, you'll also be able to use your own pod and webid to be invited to the party! These can easily be set up on the [solid community server](https://solid.community/).
- If you want to run pieces of code illustrating these examples, check out the [associated playground](https://tech.io/playgrounds/51181/common-patterns-with-solid)

### Vocabulary and concepts

Notifications in solid are based on the [Linked Data Notification (LDN) specification](https://www.w3.org/TR/ldn/):

![LDN overview](https://www.w3.org/ns/ldp/linked-data-notifications-overview.svg)

The following terms are defined by the LDN recommandation, and here is specifically how we use them in the context of solid:
- Sender: The application sending the notification
- Target: The id of the entity receiving the notification
- Receiver: The pod advertised by the Target as its own, and in particular hosting its inbox.
- Consumer: The app consuming the notification from the target's inbox
- Notification: The message itself

## First things first: inbox discovery

Notifications should be sent to inboxes, and any resource can advertize for an inbox (not only webids). It's up to the sender and to the receiver to select the appropriate resource when sending/receiving notifications. For instance, when sending a notification specifically related to Cleopatras busy schedule, one might consider addressing it to the inbox advertised by her calendar resource, `https://cleopatra.solid.community/public/calendar/inbox/`, that you can discover by examining the response to a GET on `https://cleopatra.solid.community/public/calendar`.

Let's dereference [cleopatra's webid](https://cleopatra.solid.community/profile/card#me), with a simple HTTP GET. You should be served an RDF document in which, among other things, you will find:
```
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix inbox: </inbox/>.
:me
a schem:Person, n0:Person;
...
ldp:inbox inbox:;
```

From this snippet, we see that the WebId advertises for an inbox, in this case `https://cleopatra.solid.community/inbox/`, thanks to the property `ldp:inbox`. And that's it, no we can get to notification sending!

[Try this live!](https://tech.io/playground-widget/1cee1f6e53d54b86a26dc8752218c3f018115/managing-notifications/890003/What%20is%20Cleopatra's%20inbox)

## Sending a notification

A notification is sent to the previously discovered inbox through a POST request, containing the notification body. Although it is not a strict requirement, a notification can be expressed using the ActivityStreams vocabulary (TODO: add link to voc documentation).

First, Caesar created the event [on his pod](https://jcaesar.solid.community/public/calendar/50BC/Martius/PartyAtCaesarPalace.ttl). He then sends the invite to Cleopatra's calendar inbox:
```
POST /public/calendar/inbox/ HTTPS/1.1
Host: cleopatra.solid.community
Content-Type: text/turtle
@prefix inv: <>.
@prefix as: <https://www.w3.org/ns/activitystreams#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
inv: a as:Invite;
rdfs:label "Invitation";
rdfs:comment "You are invited to my book release party";
as:object <https://jcaesar.solid.community/public/calendar/50BC/Martius/PartyAtCaesarPalace.ttl>.
```

In the response, the Location header indicates the IRI of the created event. In order to get a name nicer than a generated UUID, Caesar could have proposed a name with a Slug header in his request.

[Try this live!](https://tech.io/playground-widget/1cee1f6e53d54b86a26dc8752218c3f018115/managing-notifications/890004/Let's%20get%20crazy%20sending%20out%20invites)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
layout: post
title: Step 1: identifying the user
tags: [build]
categories: [Build on Solid]
permalink: Build-on-Solid/writing-solid-applications/1-authentication
exclude: true
---

The starting point for any Solid Web App is to obtain the user's _WebID_. The WebID is a URL at
which one can find information about the user, and where to write data to. The WebID will be provided by the user's _Identity Provider_ (which is usually their Pod Provider) after the user
gives your Web App permission to interact with their Pod.

To obtain this permission, we will be using [solid-auth-client](https://www.npmjs.com/package/solid-auth-client). Its usage is relatively straightforward:

1. Check if the user has already logged in.
2. If not, ask the user for their Identity Provider.
3. Then call `auth.login()` with the Identity Provider as the first argument.

You can ask the user for their Identity Provider using a regular `<input type="url">`, or using [a convenience library](https://www.npmjs.com/package/@solid/react/) that suggests the most commonly used Providers — at the time of writing, [solid.community](https://solid.community/) and [inrupt.net](https://inrupt.net/).

In code, that process would look roughly as follows:

```typescript
import auth from 'solid-auth-client';

async function getWebId() {
/* 1. Check if we've already got the user's WebID and access to their Pod: */
let session = await auth.currentSession();
if (session) {
return session.webId;
}

/* 2. User has not logged in; ask for their Identity Provider: */
// Implement `getIdentityProvider` to get a string with the user's Identity Provider (e.g.
// `https://inrupt.net` or `https://solid.community`) using a method of your choice.
const identityProvider = await getIdentityProvider();

/* 3. Initialise the login process - this will redirect the user to their Identity Provider: */
auth.login(identityProvider);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
layout: post
title: Step 2: Understanding Solid
tags: [build]
categories: [Build on Solid]
permalink: Build-on-Solid/writing-solid-applications/2-understanding-solid
exclude: true
---

Now that we've [got the user's WebID](./1-authentication), we have a starting point for fetching
data from the user's Pod. Before we do so, let's review the fundamentals of the concepts behind
Solid, to the extent necessary to write Solid Web Apps. If you're already familiar with Linked Data,
feel free to [skip this step](3-reading-data).

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 — similar to how HTML documents
live at a URL.

The user's WebID can be used both to identify someone — because there's only one person that
controls that URL — and as a pointer to a Document containing 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:

| Subject | Predicate | Object |
| --- | --- | --- |
| `<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.
These three together are also commonly referred to as a _Triple_.)

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:

| Subject | Predicate | Object |
| --- | --- | --- |
| `#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:

| Subject | Predicate | Object |
| --- | --- | --- |
| `#role` | label | Role |
| `#role` | comment | To specify the function or part played in a particular situation |
| `#organization-name` | label | Organization name |
| `#organization-name` | comment | To specify the organizational name |

(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:

| Subject | Predicate | Object |
| --- | --- | --- |
| `#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_", and "_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.

Phew! That should cover about all the Linked Data theory you should know to start working with
Solid. Next up: actually reading data from such a Document.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
layout: post
title: Step 3: Reading data
tags: [build]
categories: [Build on Solid]
permalink: Build-on-Solid/writing-solid-applications/3-reading-data
exclude: true
---

We've [got the user's WebID](1-authentication) and we [know that it points to a
Document](2-understanding-solid); it's time we actually read some data from that Document!

We will be using [Tripledoc](https://vincenttunru.gitlab.io/tripledoc/), which was designed
specifically for this tutorial. Other libraries exist, such as
[ldflex](https://www.npmjs.com/package/ldflex), [rdf-ext](https://www.npmjs.com/package/rdf-ext) and
[rdflib](https://www.npmjs.com/package/rdflib); Tripledoc is a thin abstraction over the latter to
aid "thinking in Solid". If, at some point in the future, you want to start combining many different
data sources, you might want to consider investigating those alternative libraries.

We will try to get the user's name as follows:

1. Fetch the Document living at their WebID.
2. From that Document, read the Subject representing the current user.
3. Get the `foaf:name` of that Subject, if set.

In code, that looks like this:

```typescript
import { fetchDocument } from 'tripledoc';

async function getName(webId) {
/* 1. Fetch the Document at `webId`: */
const webIdDoc = await fetchDocument(webId);
/* 2. Read the Subject representing the current user: */
const user = webIdDoc.getSubject(webId);
/* 3. Get their foaf:name: */
return user.getLiteral('http://xmlns.com/foaf/0.1/name')
}
```

Tip: you can avoid typing the full 'http://xmlns.com/foaf/0.1/name' every time using the library
[rdf-namespaces](https://www.npmjs.com/package/rdf-namespaces). It exports strings for the URLs of
the terms in common Vocabularies, turning the above into:

```typescript
import { fetchDocument } from 'tripledoc';
import { foaf } from 'rdf-namespaces';

async function getName(webId) {
/* 1. Fetch the Document at `webId`: */
const webIdDoc = await fetchDocument(webId);
/* 2. Read the Subject representing the current user: */
const user = webIdDoc.getSubject(webId);
/* 3. Get their foaf:name: */
return user.getLiteral(foaf.name)
}
```

Two things to note here. First, we call `getLiteral` to indicate that we are looking for an actual
value. However, the value could also have been a URL pointing to a different Subject, in which case
we could in turn fetch _that_ Document. If that was what we expected, we could have used the method
`getNodeRef` instead.

The second thing to consider is that we cannot make any assumptions about what data is or is not
present in the user's Pod. Thus, `user.getLiteral(foaf.name)` might also return `null`. This
could happen if the Document does not include the user's name, if the name is stored differently
(e.g. using `foaf:firstName` and `foaf:familyName`), or the `foaf:name` is not a Literal.

Now that we're able to read data from the user's WebID, let's find out how we can read arbitrary
other data.
Loading

0 comments on commit 39d17ae

Please sign in to comment.