DIY LDN Inbox

Linked Data Notifications is a protocol to facilitate sharing and reuse of notifications between different Web applications. It's part of a push to help people own their data and re-decentralise the Web, particularly the Social Web. You can read more about this here.

LDN is a three part protocol. We expect front-end applications and potentially servers as well to play the roles of senders and consumers of notifications. The third part is receiving. As the human, you need to tell the decentralised 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 present them back to you in a human-readable way, or to process them and trigger other tasks to run). The place you point your applications to is your Inbox. You should store your notifications data somewhere you trust. Some people might want to rent a Personal Data Store, or maybe your workplace or school supplies one to you. At the moment the commercial PDS market is.. pretty small. This data-owernship thing is in its early days.

So let's 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:

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. In this case, the files we stored them in. The JSON-LD for an Inbox listing should look like:

  {
    "@context": "http://www.w3.org/ns/ldp#",
    "@id": "",
    "@type": "ldp:Container",
    "ldp:contains": [
        {
            "@id": "https://example.org/notification1"
        },
        {
            "@id": "https://example.org/notification2"
        }
    ]
}

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 JSON-LD for 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"
                 ,"ldp: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.

🏷 hacking php tutorial https://rhiaro.co.uk/tags/linked+data+notifications ldn standards