Use standard code style.

This commit is contained in:
Julien Fontanet 2015-04-17 13:35:33 +02:00
parent c539dd5570
commit 6fa2e79c1c
12 changed files with 346 additions and 434 deletions

View File

@ -1,93 +0,0 @@
{
// Julien Fontanet JSHint configuration
// https://gist.github.com/julien-f/8095615
//
// Changes from defaults:
// - all enforcing options (except `++` & `--`) enabled
// - single quotes
// - indentation set to 2 instead of 4
// - almost all relaxing options disabled
// - environments are set to Node.js
//
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: Prohibit overwriting prototypes of native objects (Array, Date, ...)
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit use of non breakable spaces
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"maxcomplexity" : 7, // {int} Max cyclomatic complexity per function
"maxdepth" : 3, // {int} Max depth of nested blocks (within functions)
"maxlen" : 80, // {int} Max number of characters per line
"maxparams" : 4, // {int} Max number of formal params allowed per function
"maxstatements" : 20, // {int} Max number statements per function
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"multistr" : false, // true: Tolerate multi-line strings
"notypeof" : false, // true: Tolerate typeof comparison with unknown values.
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
"noyield" : false, // true: Tolerate generators without yields
// Environments
"browser" : false, // Web Browser (window, document, etc)
"browserify" : false, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : false, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mocha" : false, // mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"phantom" : false, // PhantomJS
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {} // additional predefined global variables
}

View File

@ -1,132 +1,132 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var Bluebird = require('bluebird'); var Bluebird = require('bluebird')
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter
var inherits = require('util').inherits; var inherits = require('util').inherits
var jsonRpc = require('json-rpc'); var jsonRpc = require('json-rpc')
var MethodNotFound = require('json-rpc/errors').MethodNotFound; var MethodNotFound = require('json-rpc/errors').MethodNotFound
var startsWith = require('lodash.startswith'); var startsWith = require('lodash.startswith')
var WebSocket = require('ws'); var WebSocket = require('ws')
var ConnectionError = require('./connection-error'); var ConnectionError = require('./connection-error')
var fixUrl = require('./fix-url'); var fixUrl = require('./fix-url')
//==================================================================== // ===================================================================
function getCurrentUrl() { function getCurrentUrl () {
/* global window: false */ /* global window: false */
if (typeof window === undefined) { if (typeof window === undefined) {
throw new Error('cannot get current URL'); throw new Error('cannot get current URL')
} }
return String(window.location); return String(window.location)
} }
function makeDeferred() { function makeDeferred () {
var resolve, reject; var resolve, reject
var promise = new Bluebird(function (resolve_, reject_) { var promise = new Bluebird(function (resolve_, reject_) {
resolve = resolve_; resolve = resolve_
reject = reject_; reject = reject_
}); })
return { return {
promise: promise, promise: promise,
reject: reject, reject: reject,
resolve: resolve, resolve: resolve
}; }
} }
//-------------------------------------------------------------------- // -------------------------------------------------------------------
// Low level interface to XO. // Low level interface to XO.
function Api(url) { function Api (url) {
// Super constructor. // Super constructor.
EventEmitter.call(this); EventEmitter.call(this)
// Fix the URL (ensure correct protocol and /api/ path). // Fix the URL (ensure correct protocol and /api/ path).
this._url = fixUrl(url || getCurrentUrl()); this._url = fixUrl(url || getCurrentUrl())
// Will contains the WebSocket. // Will contains the WebSocket.
this._socket = null; this._socket = null
// The JSON-RPC server. // The JSON-RPC server.
var this_ = this; var this_ = this
this._jsonRpc = jsonRpc.createServer(function (message) { this._jsonRpc = jsonRpc.createServer(function (message) {
if (message.type === 'notification') { if (message.type === 'notification') {
this_.emit('notification', message); this_.emit('notification', message)
} else { } else {
// This object does not support requests. // This object does not support requests.
throw new MethodNotFound(message.method); throw new MethodNotFound(message.method)
} }
}).on('data', function (message) { }).on('data', function (message) {
this_._socket.send(JSON.stringify(message)); this_._socket.send(JSON.stringify(message))
}); })
} }
inherits(Api, EventEmitter); inherits(Api, EventEmitter)
Api.prototype.close = function () { Api.prototype.close = function () {
if (this._socket) { if (this._socket) {
this._socket.close(); this._socket.close()
} }
}; }
Api.prototype.connect = Bluebird.method(function () { Api.prototype.connect = Bluebird.method(function () {
if (this._socket) { if (this._socket) {
return; return
} }
var deferred = makeDeferred(); var deferred = makeDeferred()
var opts = {}; var opts = {}
if (startsWith(this._url, 'wss')) { if (startsWith(this._url, 'wss')) {
// Due to imperfect TLS implementation in XO-Server. // Due to imperfect TLS implementation in XO-Server.
opts.rejectUnauthorized = false; opts.rejectUnauthorized = false
} }
var socket = this._socket = new WebSocket(this._url, '', opts); var socket = this._socket = new WebSocket(this._url, '', opts)
// Used to avoid binding listeners to this object. // Used to avoid binding listeners to this object.
var this_ = this; var this_ = this
// When the socket opens, send any queued requests. // When the socket opens, send any queued requests.
socket.addEventListener('open', function () { socket.addEventListener('open', function () {
// Resolves the promise. // Resolves the promise.
deferred.resolve(); deferred.resolve()
this_.emit('connected'); this_.emit('connected')
}); })
socket.addEventListener('message', function (message) { socket.addEventListener('message', function (message) {
this_._jsonRpc.write(message.data); this_._jsonRpc.write(message.data)
}); })
socket.addEventListener('close', function () { socket.addEventListener('close', function () {
this_._socket = null; this_._socket = null
this_._jsonRpc.failPendingRequests(new ConnectionError()); this_._jsonRpc.failPendingRequests(new ConnectionError())
// Only emit this event if connected before. // Only emit this event if connected before.
if (deferred.promise.isFulfilled()) { if (deferred.promise.isFulfilled()) {
this_.emit('disconnected'); this_.emit('disconnected')
} }
}); })
socket.addEventListener('error', function (error) { socket.addEventListener('error', function (error) {
// Fails the connect promise if possible. // Fails the connect promise if possible.
deferred.reject(error); deferred.reject(error)
}); })
return deferred.promise; return deferred.promise
}); })
Api.prototype.call = function (method, params) { Api.prototype.call = function (method, params) {
var jsonRpc = this._jsonRpc; var jsonRpc = this._jsonRpc
return this.connect().then(function () { return this.connect().then(function () {
return jsonRpc.request(method, params); return jsonRpc.request(method, params)
}); })
}; }
module.exports = Api; module.exports = Api

View File

@ -1,76 +1,76 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var Bluebird = require('bluebird'); var Bluebird = require('bluebird')
//==================================================================== // ===================================================================
function returnThis() { function returnThis () {
/* jshint validthis: true */ /* jshint validthis: true */
return this; return this
} }
// Returns an iterator to the Fibonacci sequence. // Returns an iterator to the Fibonacci sequence.
function fibonacci(start) { function fibonacci (start) {
var prev = 0; var prev = 0
var curr = start || 1; var curr = start || 1
var iterator = { var iterator = {
next: function () { next: function () {
var tmp = curr; var tmp = curr
curr += prev; curr += prev
prev = tmp; prev = tmp
return { return {
done: false, done: false,
value: prev, value: prev
}; }
}, }
}; }
// Make the iterator a true iterable (ES6). // Make the iterator a true iterable (ES6).
if (typeof Symbol !== 'undefined') { if (typeof Symbol !== 'undefined') {
iterator[Symbol.iterator] = returnThis; iterator[Symbol.iterator] = returnThis
} }
return iterator; return iterator
} }
//==================================================================== // ===================================================================
function defaultGenerator() { function defaultGenerator () {
return fibonacci(1e3); return fibonacci(1e3)
} }
function BackOff(opts) { function BackOff (opts) {
if (!opts) { if (!opts) {
opts = {}; opts = {}
} }
this._attempts = 0; this._attempts = 0
this._generator = opts.generator || defaultGenerator; this._generator = opts.generator || defaultGenerator
this._iterator = this._generator(); this._iterator = this._generator()
this._maxAttempts = opts.maxAttempts || Infinity; this._maxAttempts = opts.maxAttempts || Infinity
} }
BackOff.prototype.wait = function () { BackOff.prototype.wait = function () {
var maxAttempts = this._maxAttempts; var maxAttempts = this._maxAttempts
if (this._attempts++ > maxAttempts) { if (this._attempts++ > maxAttempts) {
return Bluebird.reject(new Error( return Bluebird.reject(new Error(
'maximum attempts reached (' + maxAttempts +')' 'maximum attempts reached (' + maxAttempts + ')'
)); ))
} }
return Bluebird.delay(this._iterator.next().value); return Bluebird.delay(this._iterator.next().value)
}; }
BackOff.prototype.reset = function () { BackOff.prototype.reset = function () {
this._attempts = 0; this._attempts = 0
this._iterator = this._generator(); this._iterator = this._generator()
}; }
//==================================================================== // ===================================================================
module.exports = BackOff; module.exports = BackOff

View File

@ -1,160 +1,160 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var forEach = require('lodash.foreach'); var forEach = require('lodash.foreach')
var indexOf = require('lodash.indexof'); var indexOf = require('lodash.indexof')
//==================================================================== // ===================================================================
function deleteProperties(obj) { function deleteProperties (obj) {
/* jshint forin: false */ /* jshint forin: false */
var prop; var prop
for (prop in obj) { for (prop in obj) {
delete obj[prop]; delete obj[prop]
} }
} }
//==================================================================== // ===================================================================
function defaultKey(item) { function defaultKey (item) {
return item.id || item._id || item; return item.id || item._id || item
} }
//==================================================================== // ===================================================================
function getAll() { function getAll () {
/* jshint validthis: true */ /* jshint validthis: true */
return this._data; return this._data
} }
function getIndexes() { function getIndexes () {
/* jshint validthis: true */ /* jshint validthis: true */
return this._indexes; return this._indexes
} }
function Collection(opts) { function Collection (opts) {
if (!opts) { if (!opts) {
opts = {}; opts = {}
} }
this._key = opts.key || defaultKey; this._key = opts.key || defaultKey
this._indexes = Object.create(null); this._indexes = Object.create(null)
if (opts.indexes) { if (opts.indexes) {
forEach(opts.indexes, function (field) { forEach(opts.indexes, function (field) {
this[field] = Object.create(null); this[field] = Object.create(null)
}, this._indexes); }, this._indexes)
} }
this._data = Object.create(null); this._data = Object.create(null)
// Expose public properties. // Expose public properties.
Object.defineProperties(this, { Object.defineProperties(this, {
all: { all: {
enumerable: true, enumerable: true,
get: getAll, get: getAll
}, },
indexes: { indexes: {
enumerable: true, enumerable: true,
get: getIndexes, get: getIndexes
}, }
}); })
} }
Collection.prototype.clear = function () { Collection.prototype.clear = function () {
deleteProperties(this._data); deleteProperties(this._data)
forEach(this._indexes, deleteProperties); forEach(this._indexes, deleteProperties)
}; }
function unsetItemFromIndex(index, field) { function unsetItemFromIndex (index, field) {
/* jshint validthis: true */ /* jshint validthis: true */
var prop = this[field]; var prop = this[field]
if (!prop) { if (!prop) {
return; return
} }
var items = index[prop]; var items = index[prop]
var i = indexOf(items, this); var i = indexOf(items, this)
if (i === -1) { if (i === -1) {
return; return
} }
// The index contains only this one item for this prop. // The index contains only this one item for this prop.
if (items.length === 1) { if (items.length === 1) {
delete index[prop]; delete index[prop]
return; return
} }
// Remove this item. // Remove this item.
items.splice(i, 1); items.splice(i, 1)
} }
// Internal unset method. // Internal unset method.
function unset(item, key) { function unset (item, key) {
/* jshint validthis: true */ /* jshint validthis: true */
delete this._data[key]; delete this._data[key]
forEach(this._indexes, unsetItemFromIndex, item); forEach(this._indexes, unsetItemFromIndex, item)
} }
function setItemToIndex(index, field) { function setItemToIndex (index, field) {
/* jshint validthis: true */ /* jshint validthis: true */
var prop = this[field]; var prop = this[field]
if (!prop) { if (!prop) {
return; return
} }
var items = index[prop]; var items = index[prop]
if (items) { if (items) {
// Update the items list. // Update the items list.
items.push(this); items.push(this)
} else { } else {
// Create the items list. // Create the items list.
index[prop] = [this]; index[prop] = [this]
} }
} }
Collection.prototype.set = function (item) { Collection.prototype.set = function (item) {
var key = this._key(item); var key = this._key(item)
if (!key) { if (!key) {
// Ignore empty keys. // Ignore empty keys.
return; return
} }
var previous = this._data[key]; var previous = this._data[key]
if (previous) { if (previous) {
unset.call(this, previous, key); unset.call(this, previous, key)
} }
this._data[key] = item; this._data[key] = item
forEach(this._indexes, setItemToIndex, item); forEach(this._indexes, setItemToIndex, item)
}; }
Collection.prototype.unset = function (item) { Collection.prototype.unset = function (item) {
var key = this._key(item); var key = this._key(item)
item = this._data[key]; item = this._data[key]
if (!item) { if (!item) {
return; return
} }
unset.call(this, item, this._key(item)); unset.call(this, item, this._key(item))
}; }
Collection.prototype.setMultiple = function (items) { Collection.prototype.setMultiple = function (items) {
forEach(items, this.set, this); forEach(items, this.set, this)
};
Collection.prototype.unsetMultiple = function (items) {
forEach(items, this.unset, this);
};
//====================================================================
function createCollection(opts) {
return new Collection(opts);
} }
module.exports = createCollection; Collection.prototype.unsetMultiple = function (items) {
forEach(items, this.unset, this)
}
// ===================================================================
function createCollection (opts) {
return new Collection(opts)
}
module.exports = createCollection

View File

@ -1,9 +1,9 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var makeError = require('make-error'); var makeError = require('make-error')
//==================================================================== // ===================================================================
module.exports = makeError('ConnectionError'); module.exports = makeError('ConnectionError')

View File

@ -1,34 +1,36 @@
var xoLib = require('./'); 'use strict'
var xoLib = require('./')
var xo = new xoLib.Xo({ var xo = new xoLib.Xo({
url: 'localhost:9000', url: 'localhost:9000'
}); })
xo.call('acl.get', {}).then(function (result) { xo.call('acl.get', {}).then(function (result) {
console.log('baz', result); console.log('baz', result)
}).catch(function (error) { }).catch(function (error) {
console.log('error', error) console.log('error', error)
}); })
xo.signIn({ xo.signIn({
email: 'admin@admin.net', email: 'admin@admin.net',
password: 'admin', password: 'admin'
}).then(function () { }).then(function () {
console.log('foo', xo.user); console.log('foo', xo.user)
}).catch(function (error) { }).catch(function (error) {
console.log('error', error) console.log('error', error)
}); })
xo.signIn({ xo.signIn({
email: 'tom', email: 'tom',
password: 'tom', password: 'tom'
}).then(function () { }).then(function () {
console.log('bar', xo.user); console.log('bar', xo.user)
}).catch(function (error) { }).catch(function (error) {
console.log('error', error) console.log('error', error)
}); })
xo.call('acl.get', {}).then(function (result) { xo.call('acl.get', {}).then(function (result) {
console.log('plop', result); console.log('plop', result)
}).catch(function (error) { }).catch(function (error) {
console.log('error', error) console.log('error', error)
}) })

View File

@ -1,21 +1,21 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
// Fix URL if necessary. // Fix URL if necessary.
var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/; var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/
function fixUrl(url) { function fixUrl (url) {
var matches = URL_RE.exec(url); var matches = URL_RE.exec(url)
var isSecure = !!matches[1]; var isSecure = !!matches[1]
var hostAndPath = matches[2]; var hostAndPath = matches[2]
var search = matches[3]; var search = matches[3]
return [ return [
isSecure ? 'wss' : 'ws', isSecure ? 'wss' : 'ws',
'://', '://',
hostAndPath, hostAndPath,
'/api/', '/api/',
search, search
].join(''); ].join('')
} }
module.exports = fixUrl; module.exports = fixUrl

View File

@ -1,48 +1,48 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var expect = require('must'); var expect = require('must')
//==================================================================== // ===================================================================
/* jshint mocha: true */ /* eslint-env mocha */
describe('fixUrl()', function () { describe('fixUrl()', function () {
var fixUrl = require('./fix-url'); var fixUrl = require('./fix-url')
describe('protocol', function () { describe('protocol', function () {
it('is added if missing', function () { it('is added if missing', function () {
expect(fixUrl('localhost/api/')).to.equal('ws://localhost/api/'); expect(fixUrl('localhost/api/')).to.equal('ws://localhost/api/')
}); })
it('HTTP(s) is converted to WS(s)', function () { it('HTTP(s) is converted to WS(s)', function () {
expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/'); expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/')
expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/'); expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/')
}); })
it('is not added if already present', function () { it('is not added if already present', function () {
expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/')
expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/'); expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/')
}); })
}); })
describe('/api/ path', function () { describe('/api/ path', function () {
it('is added if missing', function () { it('is added if missing', function () {
expect(fixUrl('ws://localhost')).to.equal('ws://localhost/api/'); expect(fixUrl('ws://localhost')).to.equal('ws://localhost/api/')
expect(fixUrl('ws://localhost/')).to.equal('ws://localhost/api/'); expect(fixUrl('ws://localhost/')).to.equal('ws://localhost/api/')
}); })
it('is not added if already present', function () { it('is not added if already present', function () {
expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/')
}); })
it('removes the hash part', function () { it('removes the hash part', function () {
expect(fixUrl('ws://localhost/#foo')).to.equal('ws://localhost/api/'); expect(fixUrl('ws://localhost/#foo')).to.equal('ws://localhost/api/')
}); })
it('conserve the search part', function () { it('conserve the search part', function () {
expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo'); expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo')
}); })
}); })
}); })

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict'
// Expose Bluebird for now to ease integration (e.g. with Angular.js). // Expose Bluebird for now to ease integration (e.g. with Angular.js).
exports.setScheduler = require('bluebird').setScheduler; exports.setScheduler = require('bluebird').setScheduler
exports.Api = require('./api'); exports.Api = require('./api')
exports.Xo = require('./xo'); exports.Xo = require('./xo')

View File

@ -19,10 +19,12 @@
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
"scripts": { "scripts": {
"test": "mocha *.spec.js" "test": "standard && mocha '*.spec.js'"
}, },
"dependencies": { "dependencies": {
"bluebird": "^2.9.6", "bluebird": "^2.9.6",
"event-to-promise": "^0.3.2",
"exec-promise": "^0.5.1",
"json-rpc": "git://github.com/julien-f/js-json-rpc#v0.3.1", "json-rpc": "git://github.com/julien-f/js-json-rpc#v0.3.1",
"lodash.assign": "^3.0.0", "lodash.assign": "^3.0.0",
"lodash.foreach": "^3.0.1", "lodash.foreach": "^3.0.1",
@ -30,10 +32,12 @@
"lodash.isstring": "^3.0.0", "lodash.isstring": "^3.0.0",
"lodash.startswith": "^3.0.0", "lodash.startswith": "^3.0.0",
"make-error": "^0.3.0", "make-error": "^0.3.0",
"pw": "0.0.4",
"ws": "^0.7.1" "ws": "^0.7.1"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^2.1.0", "mocha": "^2.1.0",
"must": "^0.12.0" "must": "^0.12.0",
"standard": "*"
} }
} }

View File

@ -1,9 +1,9 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var makeError = require('make-error'); var makeError = require('make-error')
//==================================================================== // ===================================================================
module.exports = makeError('SessionError'); module.exports = makeError('SessionError')

View File

@ -1,99 +1,99 @@
'use strict'; 'use strict'
//==================================================================== // ===================================================================
var Bluebird = require('bluebird'); var Bluebird = require('bluebird')
var isString = require('lodash.isstring'); var isString = require('lodash.isstring')
var startsWith = require('lodash.startswith'); var startsWith = require('lodash.startswith')
var Api = require('./api'); var Api = require('./api')
var BackOff = require('./back-off'); var BackOff = require('./back-off')
var ConnectionError = require('./connection-error'); var ConnectionError = require('./connection-error')
var createCollection = require('./collection'); var createCollection = require('./collection')
var SessionError = require('./session-error'); var SessionError = require('./session-error')
//==================================================================== // ===================================================================
function makeStandaloneDeferred() { function makeStandaloneDeferred () {
var resolve, reject; var resolve, reject
var promise = new Bluebird(function (resolve_, reject_) { var promise = new Bluebird(function (resolve_, reject_) {
resolve = resolve_; resolve = resolve_
reject = reject_; reject = reject_
}); })
promise.resolve = resolve; promise.resolve = resolve
promise.reject = reject; promise.reject = reject
return promise; return promise
} }
function noop() {} function noop () {}
//==================================================================== // ===================================================================
function Xo(opts) { function Xo (opts) {
if (!opts) { if (!opts) {
opts = {}; opts = {}
} else if (isString(opts)) { } else if (isString(opts)) {
opts = { opts = {
url: opts, url: opts
}; }
} }
//------------------------------------------------------------------ // -----------------------------------------------------------------
var api = new Api(opts.url); var api = new Api(opts.url)
api.on('connected', function () { api.on('connected', function () {
this._backOff.reset(); this._backOff.reset()
this.status = 'connected'; this.status = 'connected'
this._tryToOpenSession(); this._tryToOpenSession()
}.bind(this)); }.bind(this))
api.on('disconnected', function () { api.on('disconnected', function () {
this._closeSession(); this._closeSession()
this._connect(); this._connect()
}.bind(this)); }.bind(this))
api.on('notification', function (notification) { api.on('notification', function (notification) {
if (notification.method !== 'all') { if (notification.method !== 'all') {
return; return
} }
var method = ( var method = (
notification.params.type === 'exit' ? notification.params.type === 'exit' ?
'unset' : 'unset' :
'set' 'set'
) + 'Multiple'; ) + 'Multiple'
this.objects[method](notification.params.items); this.objects[method](notification.params.items)
}.bind(this)); }.bind(this))
//------------------------------------------------------------------ // -----------------------------------------------------------------
this.objects = createCollection({ this.objects = createCollection({
indexes: [ indexes: [
'ref', 'ref',
'type', 'type',
'UUID', 'UUID'
], ],
key: function (item) { key: function (item) {
return item.UUID || item.ref; return item.UUID || item.ref
}, }
}); })
this.status = 'disconnected'; this.status = 'disconnected'
this.user = null; this.user = null
this._api = api; this._api = api
this._backOff = new BackOff(); this._backOff = new BackOff()
this._credentials = opts.creadentials; this._credentials = opts.creadentials
this._session = makeStandaloneDeferred(); this._session = makeStandaloneDeferred()
this._signIn = null; this._signIn = null
//------------------------------------------------------------------ // -----------------------------------------------------------------
this._connect(); this._connect()
} }
Xo.prototype.call = function (method, params) { Xo.prototype.call = function (method, params) {
@ -102,35 +102,35 @@ Xo.prototype.call = function (method, params) {
if (startsWith(method, 'session.')) { if (startsWith(method, 'session.')) {
return Bluebird.reject( return Bluebird.reject(
new Error('session.*() methods are disabled from this interface') new Error('session.*() methods are disabled from this interface')
); )
} }
return this._session.bind(this).then(function () { return this._session.bind(this).then(function () {
return this._api.call(method, params); return this._api.call(method, params)
}).catch(ConnectionError, SessionError, function () { }).catch(ConnectionError, SessionError, function () {
// Automatically requeue this call. // Automatically requeue this call.
return this.call(method, params); return this.call(method, params)
}); })
}; }
Xo.prototype.signIn = function (credentials) { Xo.prototype.signIn = function (credentials) {
this.signOut(); this.signOut()
this._credentials = credentials; this._credentials = credentials
this._signIn = makeStandaloneDeferred(); this._signIn = makeStandaloneDeferred()
this._tryToOpenSession(); this._tryToOpenSession()
return this._signIn; return this._signIn
}; }
Xo.prototype.signOut = function () { Xo.prototype.signOut = function () {
this._closeSession(); this._closeSession()
this._credentials = null; this._credentials = null
var signIn = this._signIn; var signIn = this._signIn
if (signIn && signIn.isPending()) { if (signIn && signIn.isPending()) {
signIn.reject(new SessionError('sign in aborted')); signIn.reject(new SessionError('sign in aborted'))
} }
return this.status === 'connected' ? return this.status === 'connected' ?
@ -140,31 +140,30 @@ Xo.prototype.signOut = function () {
// Always return a promise. // Always return a promise.
Bluebird.resolve() Bluebird.resolve()
; }
};
Xo.prototype._connect = function _connect() { Xo.prototype._connect = function _connect () {
this.status = 'connecting'; this.status = 'connecting'
return this._api.connect().bind(this).catch(function (error) { return this._api.connect().bind(this).catch(function (error) {
console.warn('could not connect:', error); console.warn('could not connect:', error)
return this._backOff.wait().bind(this).then(_connect); return this._backOff.wait().bind(this).then(_connect)
}); })
}; }
Xo.prototype._closeSession = function () { Xo.prototype._closeSession = function () {
if (!this._session.isPending()) { if (!this._session.isPending()) {
this._session = makeStandaloneDeferred(); this._session = makeStandaloneDeferred()
} }
this.user = null; this.user = null
}; }
Xo.prototype._tryToOpenSession = function () { Xo.prototype._tryToOpenSession = function () {
var credentials = this._credentials; var credentials = this._credentials
if (!credentials || this.status !== 'connected') { if (!credentials || this.status !== 'connected') {
return; return
} }
this._api.call( this._api.call(
@ -174,33 +173,33 @@ Xo.prototype._tryToOpenSession = function () {
credentials credentials
).bind(this).then( ).bind(this).then(
function (user) { function (user) {
this.user = user; this.user = user
this._api.call('xo.getAllObjects').bind(this).then(function (objects) { this._api.call('xo.getAllObjects').bind(this).then(function (objects) {
this.objects.clear(); this.objects.clear()
this.objects.setMultiple(objects); this.objects.setMultiple(objects)
}); })
// Validate the sign in. // Validate the sign in.
var signIn = this._signIn; var signIn = this._signIn
if (signIn) { if (signIn) {
signIn.resolve(); signIn.resolve()
} }
// Open the session. // Open the session.
this._session.resolve(); this._session.resolve()
}, },
function (error) { function (error) {
// Reject the sign in. // Reject the sign in.
var signIn = this._signIn; var signIn = this._signIn
if (signIn) { if (signIn) {
signIn.reject(error); signIn.reject(error)
} }
} }
); )
}; }
//==================================================================== // ===================================================================
module.exports = Xo; module.exports = Xo