DIY LDN Inbox
Linked Data Notifications is a protocol to facilitate sharing and reuse of notifications between different Web applications. It's a W3C Recommendation from the Social Web Working Group, and part of a push to help people own their data and re-decentralise the Web, particularly the Social Web. You can read more about why you might want to care about this here.
For this post, I'm going to jump straight into implementation. I've chosen PHP, without any frameworks, because if you already have a (local or remote) server it should be quick for you to get going with, without needing to set up or configure anything. The "Linked Data" in the name implies involvement of RDF; in fact LDN uses JSON-LD, but I don't presume any existing understanding of these things for this post, I'll just try to introduce the minimum that you need as we go along. (For a nice intro to JSON-LD see Manu's YouTube video JSON-LD Basics.) I am assuming though you have a basic understanding of JSON, and what HTTP Headers are.
LDN is a three part protocol. We expect front-end applications as well as servers to play the roles of senders and consumers of notifications. The third part is receiving. As the human in the mix, you need to tell the applications you use where to send notifications that are meant for you (or your software to pick up), as well as where applications can read them from (in order to display them back to you, or to process them and trigger other tasks to run). This 'where' is your Inbox. Applications might, for example, discover it from your homepage or a social media profile. You should host your Inobx somewhere you trust. Just like with email, some people might want to rent space from a provider, or maybe your workplace or school supplies one to you. At the moment the market for this is.. pretty small. This Web-data-owernship thing is in its early days.
So for the pioneering developers among us, we can write our own, using around 50 lines of quick and dirty PHP.
Part 1: accepting and storing new notifications
For convenience, we're going to set some variables for URL paths we will use regularly:
$base = "https://".$_SERVER['HTTP_HOST']; // Your domain $inboxpath = "inbox"; // The directory where your notification files are stored.
First, your script needs to accept HTTP POST
requests containing JSON-LD blobs. We get the data from the php://input
path. We also get the request headers. LDN receivers need to support as a bare minimum application/ld+json
payloads, so we'll send a 415
if the Content-Type
header doesn't match this. We're also going to check the payload parses as JSON since that's an easy way to throw out (with a 400 Bad Request
) invalid JSON-LD. If you have a JSON-LD parser handy, you can validate it against that too. I haven't included one here because.. quick and dirty.
Aside: If you do have an RDF parser around, you can accept other RDF serialisations like text/turtle
. If you do, you should advertise this with an Accept-Post
HTTP header on your Inbox. I use EasyRdf for all of my RDF stuff. If you don't want to include a library there are a few services with APIs you can call, like rdf-translator.
$input = file_get_contents('php://input'); $headers = apache_request_headers(); $data = json_decode($input, true); if(strpos($headers["Content-Type"], "application/ld+json") === false){ header("HTTP/1.1 415 Unsupported Media Type"); }elseif(!$data){ header("HTTP/1.1 400 Bad Request"); echo "Invalid payload."; }else{ // Write notification contents to a file }
The LDN specification says that even if you only accept JSON-LD serialized notifications, you should set the Accept-Post
header anyway. You can do this in PHP with header("Accept-Post: application/ld+json");
or an .htaccess file with Header set Accept-Post "application/ld+json"
.
Once we've determined the payload contents are valid, we should store the notification. This is where you might want to do any or all of the following:
- Validate the notification according to its contents; maybe you want to only accept notifications of a certain
@type
, or other specific property-value (predicate-object) combinations. - Validate the notification against a data shape.
- Verify the contents by fetching URLs you find in the notification and checking that data returned from these sources corresponds with statements about them in the notification contents.
- Verify the contents by checking the statements made in the notification against statements from your own (or another trusted) knowledgebase.
- Modify the contents of the notification by adding your own provenance information, such as the date you received it.
- Store the notification contents in a database or triplestore.
- Fire off a push notification to your phone.
But for now, all we're going to do is dump the contents into a file, update the notification's @id
to point to the location we're storing it, and set the HTTP response headers:
// Write notification contents to a file $filename = $inboxpath."/".date("ymd-His")."_".uniqid().".json"; $data["@id"] = $base."/".$filename; $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); $h = fopen("../".$filename, 'w'); fwrite($h, $json); fclose($h); header("HTTP/1.1 201 Created"); header("Location: ".$base."/".$filename);
Aside: This implementation is super simplistic. The notification may come with an @id
already set, or even contain several distinct subjects, pointing to resources somewhere else on the Web. Checking that referenced resources makes the same statements as the notification you received could be good practice for verifying the truth of the notification contents. It may also be set to "@id": ""
, which is relative to request; it basically means 'this'. You don't need to add your own absolute @id
if it's already set; you can consider the URL at which you store the data as a graph URI, which contains statements about other things, but not about itself. Alternatively, you could wrap the notification data in @graph
and apply your own @id
on the top level.
Since we're storing the notifications as JSON files, you probably want to tell your server to return JSON files with Content-Type: application/ld+json
. You can do this by putting the following in a .htaccess file: AddType application/ld+json .json
.
Part 2: serving notifications
In order to make your notifications reusable by other applications, you need to expose them to GET
requests. Specifically, your Inbox needs to return a blob of JSON-LD which points to a list of the URLs from which the individual notifications can be retrieved. You probably want to put this behind some kind of access control, so that only applications with which you have authenticated can read your notifications. I use IndieAuth as a service.
In this case, the URLs in the list are the files we stored the notification data in. The JSON-LD for an Inbox listing should look like:
{ "@context": "http://www.w3.org/ns/ldp#", "@id": "", "@type": "ldp:Container", "contains": [ { "@id": "https://example.org/notification1" }, { "@id": "https://example.org/notification2" } ] }
The listing doesn't need to look identical to this, but it needs to be an equivalent JSON-LD representation. Since there are several ways of presenting the same thing in JSON-LD, you might find you use a serializer that outputs something slightly different. For example, you might see the contains part shortened to: "contains": ["https://example.org/notification1", "https://example.org/notification2"]
. You're also likely see the @context
appear differently, and prefixes for the properties (keys) might be used. The JSON-LD Playground is a good place to look at different possibilities.
Aside: The "@type": "ldp:Container"
is optional for LDN, but it helps other LDP clients understand that they might be able to use your data too.
You could store the Inbox listing in a flat file, and update it every time you receive (or delete) a notification. However, for this implementation we're going to generate it dynamically from the JSON files in our "inbox" directory. (You can take either approach if your notifications are stored in a database, too).
$files = scandir("../".$inboxpath); $notifications = array(); foreach($files as $file){ if(!is_dir($file) && substr($file, -5) == ".json"){ $notifications[] = array("@id" => $base."/".$inboxpath."/".$file); } } $inbox = array( "@context" => "http://www.w3.org/ns/ldp#" ,"@id" => "" ,"@type" => "ldp:Container" ,"contains" => $notifications ); $inboxjson = json_encode($inbox, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); header("Content-Type: application/ld+json"); echo $inboxjson;
If you want to restrict access to your notifications, this is a good place to check the request against the authentication method of your choice (eg. a token in the Authentication
header, or a signature of some kind).
Now that's all done, you can put your script on a server and check it works with the LDN Receiver test suite. If it does, submit an implementation report!
Part 3: Advertising your Inbox
In order to be useful, you need to make your Inbox discoverable by sender and consumer applications. You can do this by modifying any resource on the Web which you control (like a blog post or your website homepage) to link to the Inbox with the ldp:inbox
relation. This can be with an HTTP header:
Link: <https://example.org/inbox.php>; rel="http://www.w3.org/ns/ldp#inbox"
or RDF link, eg. JSON-LD:
{ "@context": "http://www.w3.org/ns/ldp", "@id": "https://example.org/profile", "inbox": "https://example.org/inbox.php" }
eg. RDFa:
<link href="https://example.org/inbox.php" rel="http://www.w3.org/ns/ldp#inbox" />
And that's all there is to it! The complete script is available here, for your copy-pasting pleasure (Apache 2.0 licensed).
Alternatives
If you don't fancy writing your own script to handle LDN receiving, there are few existing implementations you could self-host on your own server. Plus Linked Data Platform servers work out of the box as LDN receivers, so maybe you want to set one of those up.