-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Import current drafts of "Writing Solid apps" (#2)
* Import current drafts of "Writing Solid apps" * writing-apps Added the notification example first draft
- Loading branch information
1 parent
f25126d
commit 39d17ae
Showing
7 changed files
with
462 additions
and
0 deletions.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
_posts/learn/build-on-solid/common-patterns/1-notification.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
42 changes: 42 additions & 0 deletions
42
_posts/learn/build-on-solid/writing-solid-applications/1-authentication.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
``` |
97 changes: 97 additions & 0 deletions
97
_posts/learn/build-on-solid/writing-solid-applications/2-understanding-solid.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
70 changes: 70 additions & 0 deletions
70
_posts/learn/build-on-solid/writing-solid-applications/3-reading-data.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.