2014-08-18

JSON-LD Nits

JSON-LD is quite a handy specification. But I do have a few nits.

Per-field @vocab or @base:

Using a JSON-LD @context, it's possible to declare that the string value of any given field is itself a vocabulary term. In other words, if my JSON is something like:


{
  "@context": {
    "@vocab": "http://example.org#",
    "foo": {
      "@type": "@vocab"
    }
  },
  "foo": "bar"
}

The normalized result would be:


_:c14n0 <http://example.org#foo> <http://example.org#bar> .

Note that "bar" is expanded into "http://example.org#bar", using the in-scope @vocab. That's great, but what if the values (a) map to a different vocabulary and (b) are being migrated from a non-JSON-LD syntax (Activity Streams 1.0 for example)

Consider, for example, the following Activity Streams 2.0 document. The "verb" property needs to map to "http://activitystrea.ms/2.0/verb", while the "add" verb value itself should map to something different (the "add" verb is not actually defined as part of the Activity Streams 2.0 vocabulary). I don't want to modify the original JSON in order to interpret the "verb" properly.


{
  "@context": {
    "@vocab": "http://activitystrea.ms/2.0/",
    "verb": {
      "@type": "@vocab"
    }
  },
  "verb": "add"
}

The normalized result of this is:


_:c14n0 <http://activitystrea.ms/2.0/verb> <http://activitystrea.ms/2.0/add> .

That's obviously not what we want. Currently, the only way to solve this in JSON-LD 1.0 would be to modify the value in the original JSON. Or to map the "add" alias specifically in the @context like so:


{
  "@context": {
    "@vocab": "http://activitystrea.ms/2.0/",
    "verb": {
      "@type": "@vocab"
    },
    "add": "http://example.org/add"
  },
  "verb": "add"
}

While that works, it would require a @context that maps every possible simple verb. Which is annoying and sometimes impractical. What would be ideal is the ability to simply define a per-field @base or @vocab, like so:


{
  "@context": {
    "@vocab": "http://activitystrea.ms/2.0/",
    "verb": {
      "@base": "http://example.org/",
      "@type": "@vocab"
    }
  },
  "verb": "add"
}

This would allow me to establish a default base URI for all simple verbs without having to map each one individually or clobbering the top-level @vocab/@base of the @context itself.

@sameAs:

JSON-LD currently lacks a reliable way of performing reconciliation of object's. In order to mark an object as being the "same as" another object, implementers have to rely on other vocabularies. This is such a common use case that a new "@sameAs" JSON-LD property would be extremely useful. For instance:


{
  "@id": "http://example.org/foo",
  "@sameAs": "http://example.org/bar"
}

The interpretation of this is straightforward. This would come in very handy for vocabularies such as Activity Streams 2.0, which defines a "duplicates" property. Using the JSON-LD @context, I would simply be able to indicate that "duplicates is an alias for "@sameAs".


{
  "@context": {
    "@vocab": "http://activitystrea.ms/2.0/",
    "id": "@id",
    "duplicates": "@sameAs"
  },
  "id": "http://example.org/foo",
  "duplicates": "http://example.org/bar"
}

Yes, I know I can map duplicates to various properties in other specific vocabularies, but @sameAs is so generally useful that the ability really ought to be baked into JSON-LD directly... at least in my opinion.

@index Wonkyness:

While I appreciate @index, it is a bit wonky when converting to a normalized triple representation. For instance, consider the following:


{
  "@context": {
    "actions": {
      "@id": "http://activitystrea.ms/2.0/actions",
      "@container": "@index"
    }
  },
  "actions": {
    "foo": {
      "@id": "test"
    },
    "bar": {
      "@id": "bar"
    }
  }
}

When I pop this into the JSON-LD playground, the normalized N-Quads come out as:


_:c14n0 <http://activitystrea.ms/2.0/actions> <http://json-ld.org/playground/bar> .
_:c14n0 <http://activitystrea.ms/2.0/actions> <http://json-ld.org/playground/test> .

Note that the "foo" and "bar" field names are simply lost. That's rather crappy. The only way to get what I want is to drop the "@container":"@index" and use "@type":"@vocab", like so:


{
  "@context": {
    "actions": {
      "@id": "http://activitystrea.ms/2.0/actions",
      "@type": "@vocab"
    },
    "foo": "http://example.org/foo",
    "bar": "http://example.org/bar"
  },
  "actions": {
    "foo": {
      "@id": "test"
    },
    "bar": {
      "@id": "bar"
    }
  }
}

Which yields:


_:c14n0 <http://activitystrea.ms/2.0/actions> _:c14n1 .
_:c14n1 <http://example.org/bar> <http://json-ld.org/playground/bar> .
_:c14n1 <http://example.org/foo> <http://json-ld.org/playground/test> .

For this to work, however, I end up having to define each property that appears under actions or the JSON-LD processing will ignore it. That's not really what I want. Sure, I could define a default @vocab in the @context, but that could have some rather undesirable side effects elsewhere in the processing.

What I want is something between "@container":"@index" and "@type":"@vocab"... that allows me to declare in the context that "actions" is an "@index" but I don't want it to ignore the indexed property names when it coverts to the normalized N-Quads.

Something along the lines of:



{
  "@context": {
    "actions": {
      "@id": "http://activitystrea.ms/2.0/actions",
      "@container": "@vocab",
      "@base": "http://example.org/"
    }
  },
  "actions": {
    "foo": {
      "@id": "test"
    },
    "bar": {
      "@id": "bar"
    }
  }
}

The interpretation of "@container":"@vocab" is that the object is an @index of @vocab terms. If we combine this with the notion of per-field base URIs (see above), the resulting normalized N-Quads would look like the following (which is identical to the "@type": "@vocab" results above but does not require me to define all the properties under actions in advance or clobber my @context with a default @vocab -- which can cause other significant undesirable side effects).


_:c14n0 <http://activitystrea.ms/2.0/actions> _:c14n1 .
_:c14n1 <http://example.org/foo> <http://example.org/test> .
_:c14n1 <http://example.org/bar> <http://example.org/bar> .

In other words, while there's a somewhat hackish way to accomplish this in JSON-LD today, the current method is not overly desirable. "@container": "@vocab" and per-field @base/@vocab would provide a cleaner, more reliable solution.

(edit: while reading back through this I noticed so many errors that I'm convinced my brain was still in vacation mode. I need more coffee.)

2014-06-02

HTML imports and XSS

This email thread raises a good point: If you're doing any content filtering to prevent XSS attacks, make sure you check for <link rel="import ...> tags and make sure you're looking at the content being imported. HTML Imports *shouldn't* be any more risky than arbitrary script tags but given that the HTML import can pull in script, stylesheets and markup, you need to be careful. Best advice: treat import links exactly as you would any untrusted executable content.

2014-05-20

Activity Streams 2.0 and AppLinks

Recently I was asked how Activity Streams 2.0 Action Handlers compare with AppLinks.

For those who are not familiar, AppLinks is an emerging convention for using metadata embedded in HTML to provide "deep links" into mobile applications. You can catch the snazzy overview video on their website. Below is an example taken from their documentation:


<html>
<head>
    <meta property="al:ios:url" content="applinks://docs" />
    <meta property="al:ios:app_store_id" content="12345" />
    <meta property="al:ios:app_name" content="App Links" />
    <meta property="al:android:url" content="applinks://docs" />
    <meta property="al:android:app_name" content="App Links" />
    <meta property="al:android:package" content="org.applinks" />
    <meta property="al:web:url"
          content="http://applinks.org/documentation" />
</head>
<body>
Hello, world!
</body>
</html>

The basic idea is: whenever you want to link from one app to another, the linking app would do an HTTP GET to fetch this bit of HTML, parse out the metadata, check to see if the native app exists, and if it does, invoke it directly using the provided information. If the app doesn't exist, however, the browser is launched as a fallback. Pretty simple.

So how does this compare to Activity Streams 2.0 Actions? Well, that's pretty simple too: They are orthogonal to one another and completely compatible. We can actually take the AppLinks metadata elements and drop them directly into an Activity Streams 2.0 document. Following are two examples of this. I've created a fictional "AppLinks" objectType for the sake of the example. In the first case, AppLinks metadata is specified as an Action Handler. How this works ought to be fairly straightforward. In the second case, we use an Activity Streams 2.0 Link Value to embed the AppLinks metadata directly. Applications that don't know anything about AppLinks can safely ignore the additional markup.


{
  "objectType": "note",
  "content": "This is a note",
  "actions": {
    "view": {
      "objectType": "AppLinks",
      "al:ios:url": "applinks://docs",
      "al:ios:app_store_id": "12345",
      "al:ios:app_name": "App Links",
      "al:android:url": "applinks://docs",
      "al:android:app_name": "App Links",
      "al:android:package": "org.applinks",
      "al:web:url": "http://applinks.org/documentation"
    }
  }
}

{
  "objectType": "note",
  "content": "This is a note",
  "url": {
    "objectType": "AppLinks",
    "al:ios:url": "applinks://docs",
    "al:ios:app_store_id": "12345",
    "al:ios:app_name": "App Links",
    "al:android:url": "applinks://docs",
    "al:android:app_name": "App Links",
    "al:android:package": "org.applinks",
    "url": "http://applinks.org/documentation"   
  }
}

Simple and elegant. The way it should be.

2014-05-19

Towards a "default initial context" for AS 2.0 + JSON-LD

Just uploaded another iteration on the Activity Streams 2.0 JSON-LD @context. This takes a step towards establishing a default initial context for AS 2.0 that includes terms from the Dublin Core, FOAF, vCard, Org, Provenance, and Link Relations ontologies. While processing AS 2.0 as JSON-LD is entirely optional, this initial context ought to make it significantly easier to mix in these other vocabularies should you choose the JSON-LD approach. The updated @context is hosted here: https://w3id.org/activity-streams/v2

For example, using this context you could do something like:


{
  "@context": "https://w3id.org/activity-streams/v2",
  "objectType": {
    "id": "foaf:Person",
    "displayName": "Person"
  },
  "foaf:givenName": "James",
  "foaf:familyName": "Snell",
  "org:memberOf": "http://ibm.com",
  "prov:actingOnBehalfOf": "http://ibm.com",
  "about": "http://www.chmod777self.com"
}

The N-Quads produced would be:


<http://xmlns.com/foaf/0.1/Person> <http://activitystrea.ms/2.0/displayName> "Person" .
_:b0 <http://activitystrea.ms/2.0/objectType> <http://xmlns.com/foaf/0.1/Person> .
_:b0 <http://www.iana.org/assignments/link-relations/about> <http://www.chmod777self.com> .
_:b0 <http://www.w3.org/ns/org#memberOf> <http://ibm.com> .
_:b0 <http://www.w3.org/ns/prov#actingOnBehalfOf> <http://ibm.com> .
_:b0 <http://xmlns.com/foaf/0.1/familyName> "Snell" .
_:b0 <http://xmlns.com/foaf/0.1/givenName> "James" .

2014-05-16

IANA Link Relations JSON-LD Context

Useful new tool of the day: A simple Node app that generates a basic JSON-LD @context covering the current contents of the IANA Registry of Link Relations.

URL: http://linkrels.ng.bluemix.net/links (hosted on IBM's bluemix)

IANA: http://www.iana.org/assignments/link-relations/link-relations.xhtml

Source: https://github.com/jasnell/linkrels

Example (view using the JSON-LD Playground:


{
  "@context": "http://linkrels.ng.bluemix.net/links",
  "first": "http://example.org/first",
  "next": "http://example.org/next",
  "prev": "http://example.org/prev",
  "up": "http://example.org/up"
}

Results in the following N-Quads:


_:b0 <http://www.iana.org/assignments/link-relations/first> <http://example.org/first> .
_:b0 <http://www.iana.org/assignments/link-relations/next> <http://example.org/next> .
_:b0 <http://www.iana.org/assignments/link-relations/prev> <http://example.org/prev> .
_:b0 <http://www.iana.org/assignments/link-relations/up> <http://example.org/up> .

2014-05-13

Activity Streams 2.0 Action Handlers, Draft -06

A new draft of the Activity Streams 2.0 Action Handlers specification has been posted. This draft makes a number of important updates:

  • It introduces a new ViewActionHandler and draws a clear differentiation between HttpActionHandlers, ViewActionHandlers and EmbedActionHandlers, linking each back to the concept of Browser Contexts in HTML5, specifically:
    • HttpActionHandler is used primarily for REST/HTTP API request/responses. Invoking an HttpActionHandler has no bearing on the navigation of any browser context.
    • ViewActionHandlers are used for navigation. If you want the invocation of the action handler to result in a new popup browser window, or a new tab (generally any case where an existing or new browser context is navigated to a new location), you'd use ViewActionHandler.
    • EmbedActionHandlers are used for embedded content inline. That content could take the form of images, media files, a gadget, etc. A new subordinate browser context (i.e. an iframe) might be created.
  • It adds an HTML5 "sandbox" attribute to both the ViewActionHandler and EmbedActionHandler. This operates in exactly the same way as the sandbox attribute on HTML5 iframes. Whenever sandbox is used on an EmbedActionHandler, a new browser context (iframe) is created.
  • It defines how Action Handlers interact with the standard W3C Content Security Policy (CSP).
  • It changes the default action handler from HttpActionHandler to ViewActionHandler to properly reflect the most common use case.
  • It defines how the Base URI is established for Action Handlers as they relate to the Content Security Policy (i.e. how we determine the Origin of the Action Handler)

For example, suppose I have the following Activity Streams 2.0 object:


{
  "objectType": "note",
  "content": "Isn't this fun?",
  "actions": {
    "share": [
      {
        "objectType": "ViewActionHandler",
        "displayName": "Share Me!",
        "url": "http://example.org/share?id=123",
        "target": "_new"
      },
      {
        "objectType": "HttpActionHandler",
        "url": "http://api.example.org/share?id=123",
        "method": "POST"
      },
      {
        "objectType": "EmbedActionHandler",
        "content": "<div>...</div>",
        "mediaType": "text/html",
        "sandbox": "allow-scripts"
      }
    ]
  }
}

Here we have a note with a "share" action that has three possible Action Handlers. The ViewActionHandler is equivalent to a link. One possible way to implement the handler would be to translate it into something like: <a href="http://example.org/share?id=123">Share Me!</a>. The HttpActionHandler is equivalent to performing an XMLHttpRequest operation in the backend. The EmbedActionHandler would display the value of the content attribute in a sandboxed iframe that allows scripts to execute.

As before, the Action Handlers specification is still a work in progress so things may continue to evolve. Feedback is always welcome!

2014-05-02

More on Activity Streams 2.0 and schema.org/Actions

In my previous note, I explored ways in which we could map the Activity Streams 2.0 model into the schema.org model... here I wanted to briefly talk about going the other direction.. that is, how can be map a schema.org/Action to Activity Streams 2.0. It's fairly trivial using a JSON-LD context like the following:


{
  "@context": [
    {"@vocab":"http://schema.org/"}, {
    "as": "http://activitystrea.ms/2.0/",
    "actionStatus": {
      "@id": "as:status",
      "@type": "@vocab"
    },
    "agent": {
      "@id": "as:actor",
      "@type": "@id",
      "@container": "@set"
    },
    "instrument": {
      "@id": "as:instrument",
      "@type": "@id",
      "@container": "@set"
    },
    "location": {
      "@id": "as:location",
      "@type": "@id",
      "@container": "@set"
    },
    "object": {
      "@id": "as:object",
      "@type": "@id",
      "@container": "@set"
    },
    "participant": {
      "@id": "as:participant",
      "@type": "@id",
      "@container": "@set"
    },
    "result": {
      "@id": "as:result",
      "@type": "@id",
      "@container": "@set"
    },
    "image": {
      "@id": "as:image",
      "@type": "@id",
      "@container": "@set"
    },
    "sameAs": {
      "@id": "as:duplicates",
      "@type": "@id",
      "@container": "@set"
    },
    "url": {
      "@id": "as:url",
      "@type": "@id",
      "@container": "@set"
    },
    "name": {
      "@id": "as:displayName",
      "@container": "@language"
    },
    "description": {
      "@id": "as:summary",
      "@container": "@language"
    },
    "endTime": "as:endTime",
    "startTime": "as:startTime",
    "CompletedActionStatus": {
      "@id": "as:CompletedStatus"
    },
    "ActiveActionStatus": {
      "@id": "as:ActiveStatus"
    },
    "PotentialActionStatus": {
      "@id": "as:TentativeStatus"
    }
  }]
}

Given this context, the following bit of schema.org compliant JSON-LD:


{
  "@type": "WatchAction",
  "agent": {
    "@type": "Person",
    "name": "Sally"
  },
  "object": {
    "@type": "Movie",
    "name": "A Bug's Life"
  }
}

Yields the N-Quads:


_:c14n0 <http://activitystrea.ms/2.0/displayName> "Sally" .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
_:c14n1 <http://activitystrea.ms/2.0/actor> _:c14n0 .
_:c14n1 <http://activitystrea.ms/2.0/object> _:c14n2 .
_:c14n1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/WatchAction> .
_:c14n2 <http://activitystrea.ms/2.0/displayName> "A Bug's Life" .
_:c14n2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Movie> .

The compacted JSON-LD form becomes:


{
  "@type": "http://schema.org/WatchAction",
  "http://activitystrea.ms/2.0/actor": {
    "@type": "http://schema.org/Person",
    "http://activitystrea.ms/2.0/displayName": "Sally"
  },
  "http://activitystrea.ms/2.0/object": {
    "@type": "http://schema.org/Movie",
    "http://activitystrea.ms/2.0/displayName": "A Bug's Life"
  }
}

Which is easily translatable into the regular Activity Streams 2.0 syntax by walking backwards using the AS 2.0 JSON-LD context definition. The result, of course, is:


{
  "objectType": "http://schema.org/WatchAction",
  "actor": {
    "objectType": "http://schema.org/Person",
    "displayName": "Sally"
  },
  "object": {
    "objectType": "http://schema.org/Movie",
    "displayName": "A Bug's Life"
  }
}