diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py
index d98734b2..ebc57345 100644
--- a/mopidy/frontends/http/__init__.py
+++ b/mopidy/frontends/http/__init__.py
@@ -115,17 +115,304 @@ Example JSON-RPC request::
Example JSON-RPC response::
- {"jsonrpc": "2.0", "id": 1, "result": {"__model__": "Track", ...}}
+ {"jsonrpc": "2.0", "id": 1, "result": {"__model__": "Track", "...": "..."}}
The JSON-RPC method ``core.describe`` returns a data structure describing all
available methods. If you're unsure how the core API maps to JSON-RPC, having a
look at the ``core.describe`` response can be helpful.
-JavaScript wrapper
-==================
-A JavaScript library wrapping the JSON-RPC over WebSocket API is under
-development. Details on it will appear here when it's released.
+Mopidy.js JavaScript library
+============================
+
+We've made a JavaScript library, Mopidy.js, which wraps the WebSocket and gets
+you quickly started with working on your client instead of figuring out how to
+communicate with Mopidy.
+
+
+Getting the library
+-------------------
+
+Regular and minified versions of Mopidy.js, ready for use, is installed
+together with Mopidy. When the HTTP frontend is running, the files are
+available at:
+
+- http://localhost:6680/mopidy/mopidy.js
+- http://localhost:6680/mopidy/mopidy.min.js
+
+You may need to adjust hostname and port for your local setup.
+
+Thus, if you use Mopidy to host your web client, like described above, you can
+load the latest version of Mopidy.js by adding the following script tag to your
+HTML file:
+
+.. code-block:: html
+
+
+
+If you don't use Mopidy to host your web client, you can find the JS files in
+the Git repo at:
+
+- ``mopidy/frontends/http/data/mopidy.js``
+- ``mopidy/frontends/http/data/mopidy.min.js``
+
+If you want to work on the Mopidy.js library itself, you'll find a complete
+development setup in the ``js/`` dir in our repo. The instructions in
+``js/README.rst`` will guide you on your way.
+
+
+Creating an instance
+--------------------
+
+Once you got Mopidy.js loaded, you need to create an instance of the wrapper:
+
+.. code-block:: js
+
+ var mopidy = new Mopidy();
+
+When you instantiate ``Mopidy()`` without arguments, it will connect to
+the WebSocket at ``/mopidy/ws/`` on the current host. Thus, if you don't host
+your web client using Mopidy's web server, you'll need to pass the URL to the
+WebSocket end point:
+
+.. code-block:: js
+
+ var mopidy = new Mopidy({
+ webSocketUrl: "ws://localhost:6680/mopidy/ws/"
+ });
+
+
+Hooking up to events
+--------------------
+
+Once you have a Mopidy.js object, you can hook up to the events it emits. To
+explore your possibilities, it can be useful to subscribe to all events and log
+them:
+
+.. code-block:: js
+
+ mopidy.on(console.log);
+
+Several types of events are emitted:
+
+- You can get notified about when the Mopidy.js object is connected to the
+ server and ready for method calls, when it's offline, and when it's trying to
+ reconnect to the server by looking at the events ``state:online``,
+ ``state:offline``, ``reconnectionPending``, and ``reconnecting``.
+
+- You can get events sent from the Mopidy server by looking at the events with
+ the name prefix ``event:``, like ``event:trackPlaybackStarted``.
+
+- You can introspect what happens internally on the WebSocket by looking at the
+ events emitted with the name prefix ``websocket:``.
+
+Mopidy.js uses the event emitter library `BANE
+`_, so you should refer to BANE's
+short API documentation to see how you can hook up your listeners to the
+different events.
+
+
+Calling core API methods
+------------------------
+
+Once your Mopidy.js object has connected to the Mopidy server and emits the
+``state:online`` event, it is ready to accept core API method calls:
+
+.. code-block:: js
+
+ mopidy.on("state:online", function () [
+ mopidy.playback.next();
+ });
+
+All methods in Mopidy's :ref:`core-api` is available via Mopidy.js. The core
+API attributes is *not* available, but that shouldn't be a problem as we've
+added (undocumented) getters and setters for all of them, so you can access the
+attributes as well from JavaScript.
+
+Both the WebSocket API and the JavaScript API are based on introspection of the
+core Python API. Thus, they will always be up to date and immediately reflect
+any changes we do to the core API.
+
+The best way to explore the JavaScript API, is probably by opening your
+browser's console, and using its tab completion to navigate the API. You'll
+find the Mopidy core API exposed under ``mopidy.playback``,
+``mopidy.tracklist``, ``mopidy.playlists``, and ``mopidy.library``.
+
+All methods in the JavaScript API have an associated data structure describing
+the params it expects, and most methods also got API documentation available.
+This is available right there in the browser console, by looking at the
+method's ``description`` and ``params`` attributes:
+
+.. code-block:: js
+
+ console.log(mopidy.playback.next.params);
+ console.log(mopidy.playback.next.description);
+
+Obviously, you'll want to get a return value from many of your method calls.
+Since everything is happening across the WebSocket and maybe even across the
+network, you'll get the results asynchronously. Instead of having to pass
+callbacks and errbacks to every method you call, the methods return "promise"
+objects, which you can use to pipe the future result as input to another
+method, or to hook up callback and errback functions.
+
+.. code-block:: js
+
+ var track = mopidy.playback.getCurrentTrack();
+ // => ``track`` isn't a track, but a "promise" object
+
+Instead, typical usage will look like this:
+
+.. code-block:: js
+
+ var printCurrentTrack = function (track) {
+ if (track) {
+ console.log("Currently playing:", track.name, "by",
+ track.artists[0].name, "from", track.album.name);
+ } else {
+ console.log("No current track");
+ }
+ };
+
+ mopidy.playback.getCurrentTrack().then(printCurrentTrack, console.error);
+
+The first function passed to ``then()``, ``printCurrentTrack``, is the callback
+that will be called if the method call succeeds. The second function,
+``console.error``, is the errback that will be called if anything goes wrong.
+If you don't hook up an errback, debugging will be hard as errors will silently
+go missing.
+
+For debugging, you may be interested in errors from function without
+interesting return values as well. In that case, you can pass ``null`` as the
+callback:
+
+.. code-block:: js
+
+ mopidy.playback.next().then(null, console.error);
+
+The promise objects returned by Mopidy.js adheres to the `CommonJS Promises/A
+`_ standard. We use the
+implementation known as `when.js `_. Please
+refer to when.js' documentation or the standard for further details on how to
+work with promise objects.
+
+
+Example to get started with
+---------------------------
+
+1. Create an empty directory for your web client.
+
+2. Change the setting :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR` to point
+ to your new directory.
+
+3. Make sure that you've included
+ ``mopidy.frontends.http.HttpFrontend`` in
+ :attr:`mopidy.settings.FRONTENDS`.
+
+4. Start/restart Mopidy.
+
+5. Create a file in the directory named ``index.html`` containing e.g. "Hello,
+ world!".
+
+6. Visit http://localhost:6680/ to confirm that you can view your new HTML file
+ there.
+
+7. Include Mopidy.js in your web page:
+
+ .. code-block:: html
+
+
+
+8. Add one of the following Mopidy.js examples of how to queue and start
+ playback of your first playlist either to your web page or a JavaScript file
+ that you include in your web page.
+
+ "Imperative" style:
+
+ .. code-block:: js
+
+ var trackDesc = function (track) {
+ return track.name + " by " + track.artists[0].name +
+ " from " + track.album.name;
+ };
+
+ var queueAndPlayFirstPlaylist = function () {
+ mopidy.playlists.getPlaylists().then(function (playlists) {
+ var playlist = playlists[0];
+ console.log("Loading playlist:", playlist.name);
+ mopidy.tracklist.add(playlist.tracks).then(function (tlTracks) {
+ mopidy.playback.play(tlTracks[0]).then(function () {
+ mopidy.playback.getCurrentTrack().then(function (track) {
+ console.log("Now playing:", trackDesc(track));
+ }, console.error);
+ }, console.error);
+ }, console.error);
+ }, console.error);
+ };
+
+ var mopidy = new Mopidy(); // Connect to server
+ mopidy.on(console.log); // Log all events
+ mopidy.on("state:online", queueAndPlayFirstPlaylist);
+
+ Approximately the same behavior in a more functional style, using chaining
+ of promisies.
+
+ .. code-block:: js
+
+ var getFirst = function (list) {
+ return list[0];
+ };
+
+ var extractTracks = function (playlist) {
+ return playlist.tracks;
+ };
+
+ var printTypeAndName = function (model) {
+ console.log(model.__model__ + ": " + model.name);
+ // By returning the playlist, this function can be inserted
+ // anywhere a model with a name is piped in the chain.
+ return model;
+ };
+
+ var trackDesc = function (track) {
+ return track.name + " by " + track.artists[0].name +
+ " from " + track.album.name;
+ };
+
+ var printNowPlaying = function () {
+ // By returning any arguments we get, the function can be inserted
+ // anywhere in the chain.
+ var args = arguments;
+ return mopidy.playback.getCurrentTrack().then(function (track) {
+ console.log("Now playing:", trackDesc(track));
+ return args;
+ });
+ };
+
+ var queueAndPlayFirstPlaylist = function () {
+ mopidy.playlists.getPlaylists()
+ // => list of Playlists
+ .then(getFirst, console.error)
+ // => Playlist
+ .then(printTypeAndName, console.error)
+ // => Playlist
+ .then(extractTracks, console.error)
+ // => list of Tracks
+ .then(mopidy.tracklist.add, console.error)
+ // => list of TlTracks
+ .then(getFirst, console.error)
+ // => TlTrack
+ .then(mopidy.playback.play, console.error)
+ // => null
+ .then(printNowPlaying, console.error);
+ };
+
+ var mopidy = new Mopidy(); // Connect to server
+ mopidy.on(console.log); // Log all events
+ mopidy.on("state:online", queueAndPlayFirstPlaylist);
+
+9. The web page should now queue and play your first playlist every time your
+ load it. See the browser's console for output from the function, any errors,
+ and a all events that are emitted.
"""
# flake8: noqa