Zotero-Node
A Zotero API client package for Node.js. This package tries to make it
as easy as possible to bootstrap a Zotero client application in Node.js;
it comes with hardly any runtime dependencies and provides four simple
abstractions to interact with Zotero: Client
, Library
, Message
,
Stream
.
Clients handle the HTTPS connection to a Zotero data server, observing
any rate-limiting directives issued by the server; you can configure
settings (like API versions, default headers etc.) for each Client.
Each Library represents a Zotero user or group library and is associated
with a Client instance; a Library offers many convenience methods to
make it easy to construct Zotero API requests. Each request and the
corresponding response are then encapsulated in a Message instance, wich
provides accessors and an extendable body parser collection to handle
the various formats supported by Zotero. The Stream class, finally,
uses a WebSocket connection to access the Zotero Streaming API, manages
the subscription list locally, and automatically tries to re-open
prematurely closed connections.
Quickstart
Install the NPM package:
$ npm install zotero
And:
var zotero = require('zotero');
Now you can use the zotero.Client
and zotero.Library
constructors;
the latter is also aliased to the zotero
namespace. Let's access the
Library of Zotero's public user:
var lib = zotero({ user: '475425' });
When creating a Library you can pass-in a Client instance using the
client
property; if you don't, a default Client will be created for
you. You can access the client through the library:
> // The default HTTP headers used by the client
> lib.client.options.headers;
{ 'Zotero-API-Version': '3', 'User-Agent': 'zotero-node/0.0.1' }
> // Let's make the client re-use the TCP connection to the server
> lib.client.persist = true;
> lib.client.options.headers['Connection'];
'keep-alive'
To send requests to Zotero you can now use Library#get(path, options, callback)
,
or use the convenience methods baked into Zotero-Node:
> lib.items();
// Will call /users/475425/items
> lib.items.top({ limit: 3 })
// Will call /users/475425/items/top?limit=3
> lib.items('KWENT2ZM', { format: 'csljson' });
// Will call /users/475425/items/KWENT2ZM?format=csljson
And so on; all valid Zotero API paths can be created this way (but you can also
use the #get
method with any path yourself). All of these path methods allow
you to pass options which will be added as URL parameters. The methods will return
a Message instance; at that moment, the Message will only contain the HTTP request
which has not been sent yet, allowing you to make alterations, set event handlers
and so on. You can also pass a callback function to the request methods, which will
receive the Message instance when the response has been received and parsed.
If you just want to quickly print information about a message, pass-in
zotero.print
as the callback. It will print out information like this:
> lib.items('KWENT2ZM', { format: 'csljson' }, zotero.print);
zotero:node Path: /users/475425/items/KWENT2ZM?format=csljson +0ms
zotero:node Status: 200 +2ms
zotero:node Type: json +1ms
zotero:node Headers: {"date":"Thu, 26 Jun 2014 10:36:07 GMT","server":"Apache/2.2.15 (CentOS)","zotero-api-version":"2","content-length":"148","connection":"close","content-type":"application/vnd.citationstyles.csl+json"} +0ms
zotero:node Content: {"items":[{"id":"392648/KWENT2ZM","type":"webpage","title":"Zotero | Home","URL":"http://staging.zotero.net/","accessed":{"raw":"2011-06-28"}}]} +0ms
Message Parsing
Zotero-Node was written with the Zotero API v3 in mind and, by default, will parse
JSON responses automatically. Contents are accessible in the message.data
property
once the response has been received. Other content-types will be saved as strings
using the appropriate encoding. Having said that, it is very easy to add your own message
parsers to Zotero-Node, by adding them to zotero.Messages.parsers
. For instance,
we could add a parser for Atom responses like this:
var zotero = require('zotero');
var xml2js = require('xml2js').parseString;
zotero.Message.parsers.atom = function (data, callback) {
return xml2js(data.toString(this.encoding), callback);
};
Now, if you make a call that returns an Atom feed, it will be parsed automatically:
lib.items.top({ format: 'atom', limit: 2 }, function (error, message) {
if (error) return console.log(error.message);
console.dir(message.data.feed);
});
Stream API
Zotero-Node supports the Zotero Stream API through zotero.Stream
. To create
a single-key stream, simply pass your Zotero API key to the constructor:
var stream = new zotero.Stream({ apiKey: 'your-zotero-api-key' });
You can then register handlers for all events (e.g., topicUpdated
,
topicRemoved
, topicAdded
, subscriptionsCreated
, etc.):
stream.on('topicUpdated', function (data) {
console.log(data.topic);
console.log(data.version);
});
If you create a stream without a key, it will default to a multi-key
stream. Once the stream has been established, you can manage your
subscriptions using the .subscribe
and .unsubscribe
methods.
(new zotero.Stream())
.on('connected', function () {
this.subscribe([
{ apiKey: 'abc123' },
{ apiKey: 'efd456', topics: [ '/users/12345' ] }
]);
});
Alternatively, you can add your subscriptions even before the stream
has been connected: the respective createSubscriptions
message will
be sent automatically once the connection has been established.
You can also create a multi-key stream for a given Zotero user/group
library, by using the .stream
method on the library instance. This will
automatically create the stream and subscribe to the current library,
using the library's API key (if present):
zotero({ user: '475425' })
.stream(function (error, stream) {
// This will set up a stream and subscribe to
// the topic '/users/475425'. The callback will
// be called once the subscription has been
// accepted (or if there was an error).
});
Each stream keeps track of its subscriptions locally; call
stream.subscriptions.all
to see your current subscriptions.
You can close a stream at any time by calling stream.close()
; if the
stream is closed unexpectedly, the stream will automatically wait for
the retry
interval (sent by the Zotero API with the connected
event)
and then try to re-connect. Once the connection is established, the stream
will attempt to restore your previous subscriptions.
Rate-Limiting
Zotero-Node observes rate-limit directives by default, so you should not have
to worry about them. The headers of each response are parsed by the client; if
there are any Retry-After
or Backoff
headers, the client will switch into
limited mode; all messages you send in limited mode, will be held back, until
the limited period has expired.
If you want to check the client's state, you can do so by calling
client.state.limited
– this will return the time until the limited period
will be expired; if limited
is zero, the client is in normal mode.
If you want to force the client to send messages in limited mode, you can do
so by calling client.flush(true)
with the force flag set to true.
What about promises?
Zotero-Node uses standard Node.js style callbacks out of the box, but you can
easily promisify the API. Simply call zotero.promisify
passing in the Promise
implementation's promisify variant of your choice.
// For Bluebird:
zotero.promisify(Promise.promisify.bind(Promise));
// For Q:
zotero.promisify(Q.denodeify.bind(Q));
This will promisify the Client's request method; as a result Client#get
and
all Library getters will return a Promise instead of a Message object. If you
want to access the message object before it is sent, you can still do so: all
messages are stored in client.messages
before they are sent; messages are
added at the start of the list, so your last message will always be at index
zero in the queue.
// Using Bluebird promises, fetch the top 5 items of a library:
var promise = lib.items.top({ limit: 5 });
// If you need to modify the message before it is sent, you
// can access it at the start of the client's message queue.
lib.client.messages[0].req.getHeader('Zotero-API-Version');
// Handle the API response or errors when the promise is
// resolved or rejected:
promise
.then(function (message) {
// Handle the Zotero API response...
})
.catch(function (error) {
// Handle any errors...
});
Note that the Stream API also uses promieses now:
lib
.stream()
.then(function (stream) {
// ...
});
You can undo the promisification at any time by calling:
zotero.promisify.restore();
License
Copyright 2014-2015 Zotero. All rights reserved.
Zotero-Node is licensed under the AGPL3 license. See LICENSE for details.