Comparing version 0.0.6 to 0.1.0
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
// --- Dependencies --- | ||
@@ -27,3 +29,3 @@ | ||
* @constructor | ||
* @extends events.EventEmitter | ||
* @extends EventEmitter | ||
* | ||
@@ -35,5 +37,7 @@ * @param {Object} options - override the default configuration. | ||
this.options = extend({}, Client.defaults, options); | ||
var defaults = this.constructor.defaults; | ||
this.options = extend({}, defaults, options); | ||
this.options.headers = | ||
extend({}, Client.defaults.headers, options && options.headers); | ||
extend({}, defaults.headers, options && options.headers); | ||
@@ -55,3 +59,3 @@ this.state = new ClientState(this); | ||
host: 'api.zotero.org', | ||
port: 443, | ||
protocol: 'https', | ||
@@ -127,2 +131,3 @@ headers: { | ||
* Alternatively, the message object to send. | ||
* @param {Object|String} [body] Message body. | ||
* @param {Function} callback | ||
@@ -132,3 +137,3 @@ * | ||
*/ | ||
Client.prototype.request = function (options, callback) { | ||
Client.prototype.request = function (options, body, callback) { | ||
debug('#request %j', options); | ||
@@ -143,3 +148,3 @@ | ||
var self = this; | ||
var message = new Message(options); | ||
var message = new Message(options, body); | ||
@@ -221,3 +226,3 @@ message.once('received', function () { | ||
Client.prototype.get = function (path, options, headers, callback) { | ||
return this.http('GET', path, options, headers, callback); | ||
return this.http('GET', path, options, null, headers, callback); | ||
}; | ||
@@ -237,2 +242,3 @@ | ||
* @param {Object} [options] Request parameters. | ||
* @param {Object|String} [body] Request body. | ||
* @param {Object} [headers] Request headers. | ||
@@ -243,7 +249,73 @@ * @param {Function} [callback] | ||
*/ | ||
Client.prototype.delete = function (path, options, headers, callback) { | ||
return this.http('DELETE', path, options, headers, callback); | ||
Client.prototype.delete = function (path, options, body, headers, callback) { | ||
return this.http('DELETE', path, options, body, headers, callback); | ||
}; | ||
/** | ||
* Issues a POST request to the Zotero API. See the `#request` | ||
* method for further details. | ||
* | ||
* @method post | ||
* | ||
* @example | ||
* client.post('/users/42/items', { key: 'abc123' }, { title: ... }); | ||
* //-> posts item to /users/42/items?key=abc123 | ||
* | ||
* @param {String} path The API destination path. | ||
* @param {Object} [options] Request parameters. | ||
* @param {Object|String} [body] Request body. | ||
* @param {Object} [headers] Request headers. | ||
* @param {Function} [callback] | ||
* | ||
* @return {Message} The Zotero API message object. | ||
*/ | ||
Client.prototype.post = function (path, options, body, headers, callback) { | ||
return this.http('POST', path, options, body, headers, callback); | ||
}; | ||
/** | ||
* Issues a PUT request to the Zotero API. See the `#request` | ||
* method for further details. | ||
* | ||
* @method put | ||
* | ||
* @example | ||
* client.put('/users/42/items', { key: 'abc123' }, { title: ... }); | ||
* //-> posts item to /users/42/items?key=abc123 | ||
* | ||
* @param {String} path The API destination path. | ||
* @param {Object} [options] Request parameters. | ||
* @param {Object|String} [body] Request body. | ||
* @param {Object} [headers] Request headers. | ||
* @param {Function} [callback] | ||
* | ||
* @return {Message} The Zotero API message object. | ||
*/ | ||
Client.prototype.put = function (path, options, body, headers, callback) { | ||
return this.http('PUT', path, options, body, headers, callback); | ||
}; | ||
/** | ||
* Issues a PATCH request to the Zotero API. See the `#request` | ||
* method for further details. | ||
* | ||
* @method patch | ||
* | ||
* @example | ||
* client.put('/users/42/items', { key: 'abc123' }, { title: ... }); | ||
* //-> posts item to /users/42/items?key=abc123 | ||
* | ||
* @param {String} path The API destination path. | ||
* @param {Object} [options] Request parameters. | ||
* @param {Object|String} [body] Request body. | ||
* @param {Object} [headers] Request headers. | ||
* @param {Function} [callback] | ||
* | ||
* @return {Message} The Zotero API message object. | ||
*/ | ||
Client.prototype.patch = function (path, options, body, headers, callback) { | ||
return this.http('PATCH', path, options, body, headers, callback); | ||
}; | ||
/** | ||
* Issues a HTTP request to the Zotero API. See the `#request` | ||
@@ -258,2 +330,3 @@ * method for further details; also see `#get`, `#delete` and | ||
* @param {Object} [options] Request parameters. | ||
* @param {Object|String} [body] Request body. | ||
* @param {Object} [headers] Request headers. | ||
@@ -264,3 +337,3 @@ * @param {Function} [callback] | ||
*/ | ||
Client.prototype.http = function (method, path, options, headers, callback) { | ||
Client.prototype.http = function (method, path, options, body, headers, callback) { | ||
@@ -273,2 +346,5 @@ // Handle cases where not all arguments are specified. This is not | ||
} else if (typeof body === 'function') { | ||
callback = body; body = null; | ||
} else if (typeof options === 'function') { | ||
@@ -286,3 +362,3 @@ callback = options; options = null; | ||
}, callback); | ||
}, body, callback); | ||
}; | ||
@@ -289,0 +365,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var proxy = require('./proxy'); | ||
@@ -2,0 +4,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var proxy = require('./proxy'); | ||
@@ -2,0 +4,0 @@ |
@@ -0,3 +1,6 @@ | ||
'use strict'; | ||
// --- Dependencies --- | ||
var assert = require('assert'); | ||
var join = require('path').join; | ||
@@ -8,2 +11,4 @@ | ||
var Client = require('./client'); | ||
var Stream = require('./stream'); | ||
var proxy = require('./proxy'); | ||
@@ -13,5 +18,7 @@ var items = require('./items'); | ||
var utils = require('./utils'); | ||
var utils = require('./utils'); | ||
var extend = utils.extend; | ||
var omit = utils.omit; | ||
var omit = utils.omit; | ||
var once = utils.once; | ||
var noop = utils.noop; | ||
@@ -233,3 +240,61 @@ /** @module zotero */ | ||
/** | ||
* Connects to the Zotero Stream API and listens for | ||
* Events for this library. | ||
* | ||
* @method stream | ||
* | ||
* @param {Function} [callback] Called with the Stream | ||
* instance once the stream has been established. | ||
* | ||
* @return {Stream} A Stream instance for this library. | ||
*/ | ||
Library.prototype.stream = function (callback) { | ||
assert(this.id); | ||
callback = once(callback || noop); | ||
var stream = new Stream(); | ||
debug('connection to library stream %s...', this.prefix); | ||
function failed(reason) { | ||
debug('failed to connect to library stream: %j', reason); | ||
callback(new Error('failed to connect to library stream'), stream); | ||
} | ||
return stream | ||
.once('connected', function () { | ||
try { | ||
stream.removeListener('error', failed); | ||
var subscription = { | ||
topics: ['/' + this.prefix] | ||
}; | ||
if (this.key) | ||
subscription.apiKey = this.key; | ||
stream.subscribe(subscription, function (error, message) { | ||
if (error) return failed(error); | ||
if (message.code !== 201) { | ||
debug('status code 201 expected but was %s', message.code); | ||
return failed('failed to subscribe to library'); | ||
} | ||
debug('successfully connected to library stream'); | ||
callback(null, stream); | ||
}); | ||
} catch (reason) { | ||
failed(reason); | ||
} | ||
}.bind(this)) | ||
.on('error', failed); | ||
}; | ||
// --- Exports --- | ||
exports = module.exports = Library; |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
// --- Dependencies --- | ||
@@ -28,6 +30,12 @@ | ||
* @extends Stream | ||
* | ||
* @params {Object} [options] For `https.request`. | ||
* @params {Object} [body] Optional request body. | ||
*/ | ||
function Message(options) { | ||
function Message(options, body) { | ||
Stream.call(this); | ||
if (options) this.bind(options); | ||
if (options || body) | ||
this.bind(options, body); | ||
} | ||
@@ -247,2 +255,5 @@ | ||
* | ||
* If given, the body will be included | ||
* as a JSON string. | ||
* | ||
* @method bind | ||
@@ -252,4 +263,5 @@ * @chainable | ||
* @params {Object} options For `https.request`. | ||
* @params {Object|String} [body] Optional request body. | ||
*/ | ||
Message.prototype.bind = function (options) { | ||
Message.prototype.bind = function (options, body) { | ||
debug('binding new http request...'); | ||
@@ -279,2 +291,12 @@ | ||
if (body) { | ||
if (typeof body === 'string') { | ||
req.write(body); | ||
} else { | ||
req.write(JSON.stringify(body)); | ||
req.setHeader('Content-Type', 'application/json'); | ||
} | ||
} | ||
this.emit('request', req); | ||
@@ -281,0 +303,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var debug = require('debug')('zotero:proxy'), | ||
@@ -2,0 +4,0 @@ escape = require('querystring').escape, |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var slice = Array.prototype.slice, | ||
@@ -100,3 +102,3 @@ concat = Array.prototype.concat; | ||
return exports.pick(obj, Object.keys(obj).filter(function (key) { | ||
return exclude.indexOf(key) < 0 | ||
return exclude.indexOf(key) < 0; | ||
})); | ||
@@ -126,7 +128,8 @@ }; | ||
f.called = true; | ||
return (f.value = fn.apply(this, arguments)); | ||
} | ||
f.value = fn.apply(this, arguments); | ||
return f.value; | ||
}; | ||
}; | ||
exports.noop = function noop() {}; | ||
@@ -0,7 +1,8 @@ | ||
'use strict'; | ||
// --- Dependencies --- | ||
var debug = require('debug'); | ||
debug.enable('zotero:node'); | ||
var print = debug('zotero:node'); | ||
var prints = debug('zotero:node'); | ||
@@ -11,2 +12,3 @@ var Client = require('./client'); | ||
var Message = require('./message'); | ||
var Stream = require('./stream'); | ||
@@ -64,11 +66,11 @@ | ||
if (error) { | ||
print('API call failed: (%s) %s', error.code || 'no code', error.message); | ||
prints('API call failed: (%s) %s', error.code || 'no code', error.message); | ||
} | ||
if (message) { | ||
print('Path:\t%s', message.path); | ||
print('Status:\t%d', message.code); | ||
print('Type:\t%s', message.type); | ||
print('Headers:\t%j', message.headers); | ||
print('Content:\t\%j', message.data); | ||
prints('Path:\t%s', message.path); | ||
prints('Status:\t%d', message.code); | ||
prints('Type:\t%s', message.type); | ||
prints('Headers:\t%j', message.headers); | ||
prints('Content:\t\%j', message.data); | ||
} | ||
@@ -118,6 +120,7 @@ }; | ||
zotero.Client = Client; | ||
zotero.Client = Client; | ||
zotero.Library = Library; | ||
zotero.Message = Message; | ||
zotero.Stream = Stream; | ||
exports = module.exports = zotero; |
{ | ||
"name": "zotero", | ||
"version": "0.0.6", | ||
"version": "0.1.0", | ||
"description": "Zotero API client", | ||
@@ -30,3 +30,3 @@ "main": "lib/zotero.js", | ||
"mocha": "1.20.x", | ||
"nock": "~0.34.1", | ||
"nock": "~0.37", | ||
"should": "4.0.x", | ||
@@ -39,4 +39,5 @@ "sinon": "1.10.x", | ||
"debug": "*", | ||
"eventsource": "^0.1.4", | ||
"mime-types": "~1.0.0" | ||
} | ||
} |
@@ -41,8 +41,5 @@ Zotero-Node | ||
> // Make the client use version 2 of the Zotero API | ||
> lib.client.version = 2; | ||
> // The default HTTP headers used by the client | ||
> lib.client.options.headers; | ||
{ 'Zotero-API-Version': '2', 'User-Agent': 'zotero-node/0.0.1' } | ||
{ 'Zotero-API-Version': '3', 'User-Agent': 'zotero-node/0.0.1' } | ||
@@ -109,2 +106,43 @@ > // Let's make the client re-use the TCP connection to the server | ||
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({ key: 'your-zotero-api-key' }); | ||
You can then register handlers for all events: | ||
stream.on('topicUpdated', function (evt) { | ||
console.log(evt.data.topic); | ||
console.log(evt.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' ] } | ||
]); | ||
}); | ||
You can also create a multi-key stream for a given Zotero user or 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). | ||
}); | ||
Rate-Limiting | ||
@@ -150,3 +188,3 @@ ------------- | ||
lib.client.messages[0].req.getHeader('Zotero-API-Version'); | ||
// Handle the API response or errors when the promise is | ||
@@ -162,2 +200,10 @@ // resolved or rejected: | ||
Note that the Stream API also uses promieses now: | ||
lib | ||
.stream() | ||
.then(function (stream) { | ||
// ... | ||
}); | ||
You can undo the promisification at any time by calling: | ||
@@ -164,0 +210,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var Client = require('../lib/client'), | ||
@@ -48,2 +50,3 @@ Message = require('../lib/message'), | ||
}); | ||
describe('#get', function () { | ||
@@ -232,2 +235,28 @@ | ||
describe('#post', function () { | ||
describe('given a body object', function () { | ||
var path = '/users/475425/items'; | ||
beforeEach(function() { | ||
nock('https://api.zotero.org') | ||
.post(path, '{"title":"foo"}') | ||
.reply(200, { success: { 0: 'abc123' }}); | ||
}); | ||
it('sends a JSON body', function (done) { | ||
client.post(path, null, { title: 'foo' }, function (error, message) { | ||
(!error).should.be.true; | ||
message.code.should.eql(200); | ||
message.data.should.have.property('success'); | ||
message.data.success.should.have.property('0', 'abc123'); | ||
message.req.getHeader('content-type').should.eql('application/json'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('#state', function () { | ||
@@ -234,0 +263,0 @@ it('is not limited by default', function () { |
@@ -1,5 +0,10 @@ | ||
var Library = require('../lib/library'), | ||
Client = require('../lib/client'), | ||
sinon = require('sinon'); | ||
'use strict'; | ||
var sinon = require('sinon'); | ||
var nock = require('nock'); | ||
var Library = require('../lib/library'); | ||
var Client = require('../lib/client'); | ||
var Stream = require('../lib/stream'); | ||
describe('Zotero.Library', function () { | ||
@@ -115,7 +120,2 @@ var library; | ||
it('cannot be removed', function () { | ||
library.items = null; | ||
library.items.should.be.a.Function; | ||
}); | ||
it('calls #get with items/:id', function () { | ||
@@ -195,7 +195,2 @@ library.items('foo'); | ||
it('cannot be re-assigned', function () { | ||
library.collections = null; | ||
library.collections.should.be.a.Function; | ||
}); | ||
it('calls #get with collections/:id', function () { | ||
@@ -323,3 +318,103 @@ library.collections(); | ||
}); | ||
describe('#stream', function () { | ||
describe('when the event stream works', function () { | ||
beforeEach(function () { | ||
library.user = 12345; | ||
nock('https://stream.zotero.org') | ||
.get('/') | ||
.reply( | ||
200, | ||
'event: connected\n' + | ||
'data: {"connectionId":"foobar"}\n\n' + | ||
'event: topicUpdated\n' + | ||
'data: {"topic":"foo","version":23}\n\n', | ||
{ | ||
'Content-Type': 'text/event-stream' | ||
} | ||
); | ||
}); | ||
it('creates a library stream', function (done) { | ||
nock('https://stream.zotero.org') | ||
.post('/connections/foobar', { | ||
subscriptions: [{ | ||
topics: [ '/users/12345' ] | ||
}] | ||
}) | ||
.reply(201); | ||
var s = library.stream(function (error, stream) { | ||
(!error).should.be.true; | ||
s.should.be.equal(stream); | ||
done(); | ||
}); | ||
s.should.be.instanceof(Stream); | ||
}); | ||
it('uses the library API key if present', function (done) { | ||
nock('https://stream.zotero.org') | ||
.post('/connections/foobar', { | ||
subscriptions: [{ | ||
apiKey: 'qwerty', | ||
topics: [ '/users/12345' ] | ||
}] | ||
}) | ||
.reply(201); | ||
library.key = 'qwerty'; | ||
library.stream(function (error) { | ||
(!error).should.be.true; | ||
done(); | ||
}); | ||
}); | ||
it('fails if the subscription request fails', function (done) { | ||
nock('https://stream.zotero.org') | ||
.post('/connections/foobar') | ||
.reply(413); | ||
library.stream(function (error) { | ||
(!error).should.be.false; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('when the event stream does not work', function () { | ||
beforeEach(function () { | ||
library.user = 12345; | ||
nock('https://stream.zotero.org') | ||
.get('/') | ||
.reply( | ||
200, | ||
'event: connected\n' + | ||
'data: {}\n\n' + | ||
'event: topicUpdated\n' + | ||
'data: {"topic":"foo","version":23}\n\n', | ||
{ | ||
'Content-Type': 'text/event-stream' | ||
} | ||
); | ||
}); | ||
it('calls back with an error', function (done) { | ||
library.stream(function (error) { | ||
(!error).should.be.false; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var sinon = require('sinon'); | ||
@@ -16,3 +18,3 @@ var nock = require('nock'); | ||
it('is false if there is a next link', function () { | ||
var m = new Message(); | ||
m = new Message(); | ||
@@ -19,0 +21,0 @@ m.links = {}; |
@@ -0,8 +1,10 @@ | ||
'use strict'; | ||
var zotero = require('..'), | ||
nock = require('nock'), | ||
Promise = require('bluebird'); | ||
B = require('bluebird'); | ||
describe('When using Promises', function () { | ||
before(function () { | ||
zotero.promisify(Promise.promisify.bind(Promise)); | ||
zotero.promisify(B.promisify.bind(B)); | ||
}); | ||
@@ -37,3 +39,3 @@ | ||
it('returns the a promise object', function () { | ||
client.get(path).should.be.instanceof(Promise); | ||
client.get(path).should.be.instanceof(B); | ||
}); | ||
@@ -69,2 +71,1 @@ | ||
}); | ||
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var proxy = require('../lib/proxy'), | ||
@@ -73,2 +75,1 @@ sinon = require('sinon'); | ||
}); | ||
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var utils = require('../lib/utils'); | ||
@@ -2,0 +4,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var zotero = require('..'); | ||
@@ -2,0 +4,0 @@ //var sinon = require('sinon'); |
Sorry, the diff of this file is not supported yet
129601
27
2720
216
3
+ Addedeventsource@^0.1.4
+ Addedeventsource@0.1.6(transitive)
+ Addedoriginal@1.0.2(transitive)
+ Addedquerystringify@2.2.0(transitive)
+ Addedrequires-port@1.0.0(transitive)
+ Addedurl-parse@1.5.10(transitive)