solid-client
Javascript library for writing Solid applications. (See Changelog for version history.)
Usage
The solid client can be used by solid applications that run in the browser or on Node.js. A minified UMD bundle is provided along with the regular set of CommonJS modules.
For example:
Or, using a module loader:
var solid =
See the installation docs for more installation examples.
Take a look at the solid-client demo
page (source located in
demo/index.html
) for usage examples.
Tutorials
If you would like to learn how to build Solid apps using solid-client, please see:
Developing solid-client
Node version: 6.0+.
Install dev dependencies:
npm install
Building (uses Browserify, builds to solid-client.js
and dist/solid-client-no-rdflib.js
):
npm run build
Testing
To run the unit tests:
npm test
This runs the Tape unit test suite.
Releases
The following steps specify how to release solid-client:
Make sure you're at the HEAD of master
.
$ git checkout master && git pull
Run npm version
to bump the package version via git commit and git
tags.
# refer to http://semver.org/ for which of (major, minor, patch) to use $ npm version [major|minor|patch]
Next, push the commit and tags:
$ git push --follow-tags
Finally release the package to npmjs
.
$ npm publish
Logging In and User Profiles
Before doing any sort of reading or writing of Solid resources, your app will likely need to authenticate a user and load their profile, so let's start with those sections.
Authentication
Solid currently uses WebID-TLS for authentication, which relies on a web browser's built-in key store to manage certificates and prompt the user to select the correct certificate when accessing a server.
Solid servers must always return a Solid-specific HTTP header called User
,
which contains the WebID that
the user used to access this particular server. An empty header usually means
that the user is not authenticated.
Detecting the Current Logged-in User
Most of the WebID-TLS authentication process takes place before a web
page gets fully loaded and the javascript code has had a chance to run.
Since client-side Javascript code does not have access to most HTTP headers
(including the User
header) of the page on which it runs, how does an app
discover if there is an already authenticated user that is accessing it?
The current best practice answer is -- the app should do an Ajax/XHR HEAD request to the relevant resource:
- either to the current page if it's a standalone app, or
- to the requested resource (if it's an app that's acting as a viewer or editor, and requires a resource URI as a parameter)
For the first case (standalone apps), solid-client provides a convenience
solid.currentUser()
method (which does a HEAD request to the current page in
the background). Usage:
solid
For the second case (apps that are wrapping a resource as viewers or editors),
client apps can just use a solid.login(targetUrl)
function to return the
current user's WebID. And if users are unable to log in, prompt the user
to create an account with solid.signup()
.
Login example
Here is a typical example of authenticating a user and returning their WebID.
The following login
function, specific to your application, wraps the
solid.login()
function. If the promise is resolved, then an application
will do something with the webId
value. Otherwise, if the promise is rejected,
the application may choose to display an error message.
HTML:
Login
Javascript:
var solid = var { // Get the current user solid}
Signup example
The signup
function is very similar to the login
function, wrapping the
solid.signup()
function. It results in either a WebID or an error message
being returned.
HTML:
Sign up
Javascript:
var solid = // Signup for a WebIDvar { solid }
User Profiles
Once you have a user's WebID (say, from a login()
call), it's often useful
to load the user profile:
var profile = solid
The call to getProfile(url)
loads the full extended
profile:
the profile document itself, any sameAs
and seeAlso
links it finds there,
as well as the Preferences file.
Once a profile is loaded, you can access the values of the profile's pre-defined
fields, or look for predicates in the profile's parsed graph using
profile.find()
and profile.findAll()
:
var ns = solidvocabsolid
Profile App Registry
The profile provides an interface to the user's App Registry.
var ns = solidvocabsolid
User Type Registry Index
If your application needs to do data discovery, it can also call
loadTypeRegistry()
after loading the profile:
var profile = solid
Now, both listed and unlisted type indexes are loaded, and you can look up where the user keeps various types.
var ns = solidvocab// .. load profile and load type registry var addressBookRegistrations = solid /*-->[ an IndexRegistration( locationUri: 'https://localhost:8443/public-contacts/AddressBook.ttl', locationType: 'instance', isListed: true ), an IndexRegistration( locationUri: 'https://localhost:8443/personal-address-books/', locationType: 'container', isListed: false )]*/
You can then load the resources from the returned locations, as usual.
addressBookRegistrations
Registering (and un-registering) Types in the Type Registry
To register an RDF Class with a user's Type Registry (listed or unlisted),
use profile.registerType()
:
var ns = solidvocab// .. load profile var classToRegister = vocabvar locationToRegister = 'https://localhost:8443/new-posts-container/'var isListed = trueprofile // To remove the same class from registry:var classToRemove = nsprofile
Web operations
solid-client uses a mix of LDP and Solid-specific functions to manipulate Web resources. Please see the Solid spec for more details.
Getting information about a resource
Sometimes an application may need to get some useful meta data about a resource.
For instance, it may want to find out where the ACL resource is. Clients should
take notice to the fact that the solid.web.head()
function will always
successfully complete, even for resources that don't exists, since that is
considered useful information. For instance, clients can use the
solidResponse.xhr.status
value will indicate whether the resource exists or
not.
Here, for example, we can find out where the corresponding ACL resource is for
our new blog post hello-world
.
var solid = var url = 'https://example.org/blog/hello-world'solidweb
The SolidResponse
object returned by most solid.web
calls, including
head()
, contains the following properties:
url
- the URL of the resource //https://example.org/blog/hello-world
acl
- the URL of the corresponding .acl resource //https://example.org/blog/hello-world.acl
meta
- the URL of the corresponding .meta resource //https://example.org/blog/hello-world.meta
types
- An array of LDP types for the resource, if applicable. For example:[ 'http://www.w3.org/ns/ldp#LDPResource', 'http://www.w3.org/ns/ldp#Resource' ]
user
- the WebID of the authenticated user (if authenticated) //https://user.example.org/profile#me
websocket
- the URI of the corresponding websocket instance //wss://example.org/blog/hello-world
method
- the HTTP verb (get
,put
, etc) of the original request that resulted in this response.xhr
- the raw XMLHttpRequest object (e.g. xhr.status)
The response object also has some convenience methods:
contentType()
- returns the MIME type of the resourceisContainer()
- determines whether the resource is a Container or a regular resource
Fetching a Resource
Assuming that a resource or a container exists (see
creating resources and
creating containers below), you can retrieve
it using web.get()
:
solidweb
Fetching a Parsed Graph
Once a resource is retrieved, we can access it as a parsed graph (here, parsed
by rdflib.js
). This graph can then be queried.
var solid = var vocab = solidvocab var url = 'https://example.org/blog/hello-world' solidweb
Creating a Solid Container
The Solid client offers a function called solid.web.createContainer()
,
which is used to create containers. The
function accepts the following parameters:
parentDir
(string) - the URL of the parent container in which the new resource/container will be created.containerName
(string) (optional) - the value for theSlug
header -- i.e. the name of the new resource to be created; this value is optional.options
(object) - Optional hashmap of request optionsdata
(string) - Optional RDF data serialized astext/turtle
; can also be an empty string if no data will be sent.
In the example below we are also sending some meta data (semantics) about the
container, setting its type to sioc:Blog
.
// Assumes you've loaded rdflib.js and solid-client, see Dependences abovevar solid = var parentUrl = 'https://example.org/'var containerName = 'blog'var options = {}var data = '<#this> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://rdfs.org/sioc/ns#Blog> .' solidweb
Note that the options
and data
parameters are optional, and you can simply
do solid.web.createContainer(url, name)
.
Listing a Solid Container
To list the contents of a Solid container, just use solid.web.get()
.
This returns a promise that resolves to a SolidContainer
instance,
which will contain various useful properties:
- A short name (
.name
) and absolute URI (.uri
) - A
.parsedGraph
property for further RDF queries - A parsed list of links to all the contents (both containers and resources)
(
.contentsUris
) - A list of RDF types to which the container belongs (
.types
) - A hashmap of all sub-Containers within this container, keyed by absolute uri
(
.containers
) - A hashmap of all non-container Resources within this container, also keyed by
absolute uri. (
.resources
)
Containers also have several convenience methods:
container.isEmpty()
will returntrue
when there are no sub-containers or resources inside itcontainer.findByType(rdfClass)
will return an array of resources or containers that have the givenrdfClass
in their.types
array
For example:
var container = solidweb // container is an instance of SolidContainer (see lib/solid/container.js)containeruri // -> 'https://localhost:8443/settings/'containername // -> 'settings'container // -> falsecontainertypes // -> 'http://www.w3.org/ns/ldp#BasicContainer' 'http://www.w3.org/ns/ldp#Container'containercontentsUris // -> 'https://localhost:8443/settings/prefs.ttl' 'https://localhost:8443/settings/privateTypeIndex.ttl' 'https://localhost:8443/settings/testcontainer/' var subContainer = containercontainers'https://localhost:8443/settings/testcontainer/'subContainername // -> 'testcontainer'subContainertypes // -> 'http://www.w3.org/ns/ldp#BasicContainer' 'http://www.w3.org/ns/ldp#Container' 'http://www.w3.org/ns/ldp#Resource' var resource = containerresources'https://localhost:8443/settings/privateTypeIndex.ttl'// resource - SolidResource instanceresourcename // -> 'privateTypeIndex.ttl'resourcetypes // -> 'http://www.w3.org/ns/ldp#Resource' 'http://www.w3.org/ns/solid/terms#TypeIndex' 'http://www.w3.org/ns/solid/terms#UnlistedDocument'resource // -> true container // -> // a SolidContainer('testcontainer'), // a SolidResource('privateTypeIndex.ttl'), // a SolidResource('prefs.ttl')
Creating a resource
Creating a regular LDP resource is done using the web.post()
method.
In this example we will create the resource hello-world
under the newly
created blog/
container.
var solid = var parentDir = 'https://example.org/blog/'var slug = 'hello-world'var data = `<> a <http://rdfs.org/sioc/ns#Post> ; <http://purl.org/dc/terms/title> "First post" ; <http://rdfs.org/sioc/ns#content> "Hello world! This is my first post" .` solidweb
Updating a resource
Sometimes we need to update a resource after making a small change. For
instance, we sometimes need to delete a triple, or update the value of an object
(technically by replacing the triple with a new one). Luckily, Solid allows us
to use the HTTP PATCH
operation to do very small changes.
Let's try to change the value of the title in our first post. To do so, we need to indicate which triple we want to replace, and then the triple that will replace it.
Let's create the statements and serialize them to Turtle before patching the blog post resource:
var rdf = var url = 'https://example.org/blog/hello-world'var vocab = nsvocab var oldTitleTriple = rdf var newTitleTriple = rdf
Now we can actually patch the resource. The solid.web.patch()
function (also
aliased to solid.web.update()
) takes three arguments:
url
(string) - the URL of the resource to be overwritten.toDel
(array) - an array of statements to be deleted, serialized as Turtle.toIns
(array) - an array of statements to be inserted, serialized as Turtle.
var solid = var toDel = oldTitleTriple var toIns = newTitleTriple solidweb
Replacing a resource
We can also completely replace (overwrite) existing resources with new content,
using the client's solid.web.put()
function (also aliased to replace()
). The
function accepts the following parameters:
url
(string) - the URL of the resource to be overwritten.data
(string) - RDF data serialized astext/turtle
; can also be an empty string if no data will be sent.mime
(string) (optional) - the mime type for this resource; this value is optional and defaults totext/turtle
.
Here is an example where we try to overwrite the existing resource
hello-world
, giving it a bogus type - http://example.org/#Post
.
var solid = var url = 'https://example.org/blog/hello-world'var data = '<> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/#Post> .' solidweb
Deleting a resource
Delete an RDF resource from the Web. For example, we can delete the blog post
hello-world
we created earlier, using the solid.web.del()
function.
NOTE: while this function can also be used to delete containers, it will only work for empty containers. For now, app developers should make sure to empty a container by recursively calling calling this function on its contents.
var solid = var url = 'https://example.org/blog/hello-world' solidweb
Managing Resource Permissions
Each Solid resource has a set of permissions that determine which user
(identified by their WebID) has read and write access to it, called an
ACL resource.
(See the web-access-control-spec
repo
for the exact details.)
solid-client has a set of convenience methods to help developers manage those permissions.
Reading Permissions
To load the corresponding ACL resource, for a given file:
var solid = var resourceUrl = 'https://example.org/blog/hello-world' solid
Note: You can read the permissions for a given resource only if you have
acl:Control
access mode for that resource. (You also need that access mode to
edit those permissions, as well.)
You can also access individual authorizations from a resource set:
solid
Editing Permissions
To manage the set of permissions for a given resource (provided the current
user has acl:Control
access mode granted to them for that resource), use
the convenience methods provided by PermissionSet
.
The example below adds 3 different permissions:
- Allows Alice to Read, Write and Control the resource
- Allows Public Read access (that's the
solid.acl.EVERYONE
) - Grants Bob Write access (in addition to the Read access he inherits from the above permission, since he's a member of the Public). Also, this Write access is only allowed from a particular origin.
var solid = var resourceUrl = 'https://example.org/blog/hello-world'var aliceWebId = 'https://alice.example.org/profile/card#me'var bobWebId = 'https://bob.example.org/profile/card#me'var allowedOrigin = 'https://example.org' solid
To delete all permissions associated with a resource, use
clearPermissions()
. Keep in mind that permissions are inherited from a
resource's parent container, and if you delete an individual ACL resource,
this simply means that the permissions reset to that of the upstream container.
You can also clear the ACLs of the container, all the way up to the root storage
container's ACL, which cannot be deleted. Refer to the
ACL Inheritance Algorithm
section of the spec.
// If you have an existing PermissionSet as a result of `getPermissions()`:solid // Otherwise, use the helper function// solid.clearPermissions(resourceUrl) insteadsolid