From 0289cc3ee5add8b806f3e8090d510380cedb826e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 26 Jul 2014 10:15:29 +0200 Subject: [PATCH 001/134] Initial commit. --- packages/xo-lib/.editorconfig | 12 +++ packages/xo-lib/.gitignore | 1 + packages/xo-lib/.jshintrc | 118 ++++++++++++++++++++++++++ packages/xo-lib/README.md | 58 +++++++++++++ packages/xo-lib/index.js | 153 ++++++++++++++++++++++++++++++++++ packages/xo-lib/package.json | 33 ++++++++ 6 files changed, 375 insertions(+) create mode 100644 packages/xo-lib/.editorconfig create mode 100644 packages/xo-lib/.gitignore create mode 100644 packages/xo-lib/.jshintrc create mode 100644 packages/xo-lib/README.md create mode 100644 packages/xo-lib/index.js create mode 100644 packages/xo-lib/package.json diff --git a/packages/xo-lib/.editorconfig b/packages/xo-lib/.editorconfig new file mode 100644 index 000000000..e35119a31 --- /dev/null +++ b/packages/xo-lib/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespaces = true diff --git a/packages/xo-lib/.gitignore b/packages/xo-lib/.gitignore new file mode 100644 index 000000000..2ccbe4656 --- /dev/null +++ b/packages/xo-lib/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/packages/xo-lib/.jshintrc b/packages/xo-lib/.jshintrc new file mode 100644 index 000000000..efb65216f --- /dev/null +++ b/packages/xo-lib/.jshintrc @@ -0,0 +1,118 @@ +{ + // -------------------------------------------------------------------- + // JSHint Configuration, Node.js Edition + // -------------------------------------------------------------------- + // + // This is an options template for [JSHint][1], forked from + // haschek's [JSHint template][2]: + // + // * the environment has been changed to `node`; + // * recent options were added; + // * coding style has been adapted to node (e.g. 2 spaces + // indenting, global use strict). + // + // [1]: http://www.jshint.com/ + // [2]: https://gist.github.com/haschek/2595796 + // + // @author Julien Fontanet + // @license http://unlicense.org/ + + // == Enforcing Options =============================================== + // + // These options tell JSHint to be more strict towards your code. Use + // them if you want to allow only a safe subset of JavaScript, very + // useful when your codebase is shared with a big number of developers + // with different skill levels. + + "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). + "camelcase" : true, // Require variable names to use either camelCase or UPPER_CASE styles. + "curly" : true, // Require {} for every new block or scope. + "eqeqeq" : true, // Require triple equals i.e. `===`. + "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. + "freeze" : true, // Prohibit modification of native objects' prototypes. + "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` + "indent" : 2, // Specify indentation spacing + "latedef" : true, // Prohibit variable use before definition. + "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. + "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. + "noempty" : true, // Prohibit use of empty blocks. + "nonew" : true, // Prohibit use of constructors for side-effects. + "plusplus" : false, // Prohibit the use of `++` & `--`. + "quotmark" : "'", // Require single quotes. + "undef" : true, // Require all non-global variables be declared before they are used. + "unused" : true, // Prohibit unused variables. + "strict" : true, // Require `use strict` pragma in every function. + "trailing" : true, // Prohibit trailing whitespaces. + "maxparams" : 4, // Prohibit more than 4 parameters per function definition. + "maxdepth" : 3, // Prohibit nesting more than 3 control blocks. + "maxstatements" : 20, // Prohibit more than 20 statements per function. + "maxcomplexity" : 7, // Prohibit having to much branches in your code. + "maxlen" : 80, // Prohibit line with more than 80 characters. + + // == Relaxing Options ================================================ + // + // These options allow you to suppress certain types of warnings. Use + // them only if you are absolutely positive that you know what you are + // doing. + + "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). + "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. + "debug" : false, // Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // Tolerate use of `== null`. + "esnext" : false, // Allow ES.next specific features such as `const` and `let`. + "evil" : false, // Tolerate use of `eval`. + "expr" : true, // Tolerate `ExpressionStatement` as Programs. (Allowed for Mocha.) + "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. + "gcl" : false, // Makes JSHint compatible with Google Closure Compiler. + "globalstrict" : true, // Allow global "use strict" (also enables 'strict'). + "iterator" : false, // Allow usage of __iterator__ property. + "lastsemic" : false, // Tolerate missing semicolons when the it is omitted for the last statement in a one-line block. + "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. + "laxcomma" : false, // Suppress warnings about comma-first coding style. + "loopfunc" : false, // Allow functions to be defined within loops. + "maxerr" : 50, // Maximum errors before stopping. + "moz" : false, // Tolerate Mozilla JavaScript extensions. + "notypeof" : false, // Tolerate invalid typeof values. + "multistr" : false, // Tolerate multi-line strings. + "proto" : false, // Tolerate __proto__ property. This property is deprecated. + "scripturl" : false, // Tolerate script-targeted URLs. + "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignment only. + "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. + "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. + "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. + "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. + + // == Environments ==================================================== + // + // These options pre-define global variables that are exposed by + // popular JavaScript libraries and runtime environments—such as + // browser or node.js. + + "browser" : false, // Standard browser globals e.g. `window`, `document`. + "couch" : false, // Enable globals exposed by CouchDB. + "devel" : false, // Allow development statements e.g. `console.log();`. + "dojo" : false, // Enable globals exposed by Dojo Toolkit. + "jquery" : false, // Enable globals exposed by jQuery JavaScript library. + "mocha" : true, // Enable globals exposed by the mocha test runner. + "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. + "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. + "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. + "phantom" : false, // Enable globals exposed by PhantomJS. + "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. + "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. + "worker" : false, // Enable globals exposed when running inside a Web Worker. + "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. + "yui" : false, // Enable globals exposed by YUI. + + // == JSLint Legacy =================================================== + // + // These options are legacy from JSLint. Aside from bug fixes they will + // not be improved in any way and might be removed at any point. + + "nomen" : false, // Prohibit use of initial or trailing underbars in names. + "onevar" : false, // Allow only one `var` statement per function. + "passfail" : false, // Stop on first error. + "white" : false, // Check against strict whitespace and indentation rules. + + "globals": {} +} diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md new file mode 100644 index 000000000..4f045eaec --- /dev/null +++ b/packages/xo-lib/README.md @@ -0,0 +1,58 @@ +# xo-lib + +[![Build Status](https://img.shields.io/travis/vatesfr/xo-lib/master.svg)](http://travis-ci.org/vatesfr/xo-lib) +[![Dependency Status](https://david-dm.org/vatesfr/xo-lib/status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-lib) +[![devDependency Status](https://david-dm.org/vatesfr/xo-lib/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-lib#info=devDependencies) + +> Library to connect to XO-Server. + + +## Install + +Download [manually](https://github.com/vatesfr/xo-lib/releases) or with package-manager. + +#### [npm](https://npmjs.org/package/xo-lib) + +``` +npm install --save xo-lib +``` + +#### bower + +``` +bower install --save xo-lib +``` + +## Example + +```javascript +var Xo = require('xo-lib'); + +var xo = new Xo('https://xo.company.tld/api/'); + +xo.call('session.signInWithPassword', { + email: 'admin@admin.net', + password: 'admin', +}).then(function () { + return xo.call('session.getUser'); +}).then(function (user) { + console.log(user); + + xo.close(); +}); +``` + +## Contributions + +Contributions are *very* welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xo-lib/issues) + you've encountered; +- fork and create a pull request. + +## License + +ISC © [Vates SAS](http://vates.fr) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js new file mode 100644 index 000000000..74b155e7c --- /dev/null +++ b/packages/xo-lib/index.js @@ -0,0 +1,153 @@ +'use strict'; + +//==================================================================== + +var assign = require('lodash.assign'); +var forEach = require('lodash.foreach'); + +var Promise = require('bluebird'); + +// Support browsers. +var WebSocket = (function (root) { + return (root && 'WebSocket' in root) ? + root.WebSocket : + require('ws') + ; +})(this); + +//==================================================================== + +var notConnected = function () { + throw new Error('not connected'); +}; + +//==================================================================== + +var Xo = function (url) { + this._url = url; + + // Identifier of the next request. + this._nextId = 0; + + // Promises linked to the requests. + this._deferreds = {}; + + // Current WebSocket. + this._socket = null; + + // Current status which may be: + // - disconnected + // - connecting + // - connected + this.status = 'disconnected'; +}; + +assign(Xo.prototype, { + close: function () { + if (this._socket) + { + this._socket.close(); + } + }, + + connect: function () { + if (this.status === 'connected') + { + return Promise.cast(); + } + + var deferred = Promise.defer(); + + this.status = 'connecting'; + + var socket = this._socket = new WebSocket(this._url); + + // When the socket opens, send any queued requests. + socket.on('open', function () { + this.status = 'connected'; + + // (Re)Opens accesses. + delete this.send; + + // Resolves the promise. + deferred.resolve(); + }.bind(this)); + + socket.on('message', function (data) { + // `ws` API is lightly different from standard API. + if (data.data) + { + data = data.data; + } + + // TODO: Wraps in a promise to prevent releasing the Zalgo. + var response = JSON.parse(data); + + var id = response.id; + + var deferred = this._deferreds[id]; + if (!deferred) + { + // Response already handled. + return; + } + delete this._deferreds[id]; + + if ('error' in response) + { + return deferred.reject(response.error); + } + + if ('result' in response) + { + return deferred.resolve(response.result); + } + + deferred.reject({ + message: 'invalid response received', + object: response, + }); + }.bind(this)); + + socket.on('close', function () { + // Closes accesses. + this.send = notConnected; + + // Fails all waiting requests. + forEach(this._deferreds, function (deferred) { + deferred.reject('not connected'); + }); + this._deferreds = {}; + }.bind(this)); + + socket.on('error', function (error) { + // Fails the connect promise if possible. + deferred.reject(error); + }); + + return deferred.promise; + }, + + call: function (method, params) { + return this.connect().then(function () { + var socket = this._socket; + + var id = this._nextId++; + + socket.send(JSON.stringify({ + jsonrpc: '2.0', + id: id, + method: method, + params: params || [], + })); + + var deferred = this._deferreds[id] = Promise.defer(); + + return deferred.promise; + }.bind(this)); + }, +}); + +//==================================================================== + +module.exports = Xo; diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json new file mode 100644 index 000000000..7d121e530 --- /dev/null +++ b/packages/xo-lib/package.json @@ -0,0 +1,33 @@ +{ + "name": "xo-lib", + "version": "0.0.0", + "description": "Library to connect to XO-Server", + "keywords": [ + "xen", + "orchestra", + "xen-orchestra" + ], + "repository": { + "type": "git", + "url": "https://github.com/vatesfr/xo-lib" + }, + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@vates.fr" + }, + "engines": { + "node": ">=0.8.0" + }, + "scripts": { + "test": "gulp test" + }, + "files": [ + "index.js" + ], + "dependencies": { + "bluebird": "^2.2.2", + "lodash.assign": "^2.4.1", + "lodash.foreach": "^2.4.1", + "ws": "^0.4.31" + } +} From 1893061c0d27a2822157c83afc1c90f2eb750262 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 26 Jul 2014 10:15:38 +0200 Subject: [PATCH 002/134] 0.1.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 7d121e530..f1552f73c 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.0.0", + "version": "0.1.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 6373d667d95df8f3611c350e09fb60017a6f7615 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 26 Jul 2014 23:42:04 +0200 Subject: [PATCH 003/134] Work around XO-Server incomplete TLS --- packages/xo-lib/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 74b155e7c..4e4bbeaeb 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -60,7 +60,10 @@ assign(Xo.prototype, { this.status = 'connecting'; - var socket = this._socket = new WebSocket(this._url); + var socket = this._socket = new WebSocket(this._url, { + // Due to imperfect TLS implementation in XO-Server. + rejectUnauthorized: false, + }); // When the socket opens, send any queued requests. socket.on('open', function () { From 84a4242e271f18a31165ebb5160cfb1eac3d2043 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 26 Jul 2014 23:48:53 +0200 Subject: [PATCH 004/134] 0.1.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index f1552f73c..4c863a0c0 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.1.0", + "version": "0.1.1", "description": "Library to connect to XO-Server", "keywords": [ "xen", From e8cb4f90f459183fad60b656b4b3cb15081f904c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 28 Jul 2014 13:21:07 +0200 Subject: [PATCH 005/134] Update JSHint conf. --- packages/xo-lib/.jshintrc | 190 ++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 109 deletions(-) diff --git a/packages/xo-lib/.jshintrc b/packages/xo-lib/.jshintrc index efb65216f..97fda4de7 100644 --- a/packages/xo-lib/.jshintrc +++ b/packages/xo-lib/.jshintrc @@ -1,118 +1,90 @@ { - // -------------------------------------------------------------------- - // JSHint Configuration, Node.js Edition - // -------------------------------------------------------------------- + // Julien Fontanet JSHint configuration // - // This is an options template for [JSHint][1], forked from - // haschek's [JSHint template][2]: + // Changes from defaults: + // - all enforcing options (except `++` & `--`) enabled + // - single quotes + // - indentation set to 2 instead of 4 + // - almost all relaxing options disabled + // - allow expression statements (necessary for chai.expect()) + // - allow global strict (most of my devs are in Node.js or Browserify) + // - environments are set to Browserify, mocha & Node.js // - // * the environment has been changed to `node`; - // * recent options were added; - // * coding style has been adapted to node (e.g. 2 spaces - // indenting, global use strict). - // - // [1]: http://www.jshint.com/ - // [2]: https://gist.github.com/haschek/2595796 - // - // @author Julien Fontanet - // @license http://unlicense.org/ + // See http://jshint.com/docs/ for more details - // == Enforcing Options =============================================== - // - // These options tell JSHint to be more strict towards your code. Use - // them if you want to allow only a safe subset of JavaScript, very - // useful when your codebase is shared with a big number of developers - // with different skill levels. + "maxerr" : 50, // {int} Maximum error before stopping - "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). - "camelcase" : true, // Require variable names to use either camelCase or UPPER_CASE styles. - "curly" : true, // Require {} for every new block or scope. - "eqeqeq" : true, // Require triple equals i.e. `===`. - "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. - "freeze" : true, // Prohibit modification of native objects' prototypes. - "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` - "indent" : 2, // Specify indentation spacing - "latedef" : true, // Prohibit variable use before definition. - "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. - "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. - "noempty" : true, // Prohibit use of empty blocks. - "nonew" : true, // Prohibit use of constructors for side-effects. - "plusplus" : false, // Prohibit the use of `++` & `--`. - "quotmark" : "'", // Require single quotes. - "undef" : true, // Require all non-global variables be declared before they are used. - "unused" : true, // Prohibit unused variables. - "strict" : true, // Require `use strict` pragma in every function. - "trailing" : true, // Prohibit trailing whitespaces. - "maxparams" : 4, // Prohibit more than 4 parameters per function definition. - "maxdepth" : 3, // Prohibit nesting more than 3 control blocks. - "maxstatements" : 20, // Prohibit more than 20 statements per function. - "maxcomplexity" : 7, // Prohibit having to much branches in your code. - "maxlen" : 80, // Prohibit line with more than 80 characters. + // 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() + "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 + "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" : true, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : 4, // {int} Max number of formal params allowed per function + "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) + "maxstatements" : 20, // {int} Max number statements per function + "maxcomplexity" : 7, // {int} Max cyclomatic complexity per function + "maxlen" : 80, // {int} Max number of characters per line - // == Relaxing Options ================================================ - // - // These options allow you to suppress certain types of warnings. Use - // them only if you are absolutely positive that you know what you are - // doing. + // 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` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : true, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : true, // 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 + "multistr" : false, // true: Tolerate multi-line strings + "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 - "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). - "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. - "debug" : false, // Allow debugger statements e.g. browser breakpoints. - "eqnull" : false, // Tolerate use of `== null`. - "esnext" : false, // Allow ES.next specific features such as `const` and `let`. - "evil" : false, // Tolerate use of `eval`. - "expr" : true, // Tolerate `ExpressionStatement` as Programs. (Allowed for Mocha.) - "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. - "gcl" : false, // Makes JSHint compatible with Google Closure Compiler. - "globalstrict" : true, // Allow global "use strict" (also enables 'strict'). - "iterator" : false, // Allow usage of __iterator__ property. - "lastsemic" : false, // Tolerate missing semicolons when the it is omitted for the last statement in a one-line block. - "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. - "laxcomma" : false, // Suppress warnings about comma-first coding style. - "loopfunc" : false, // Allow functions to be defined within loops. - "maxerr" : 50, // Maximum errors before stopping. - "moz" : false, // Tolerate Mozilla JavaScript extensions. - "notypeof" : false, // Tolerate invalid typeof values. - "multistr" : false, // Tolerate multi-line strings. - "proto" : false, // Tolerate __proto__ property. This property is deprecated. - "scripturl" : false, // Tolerate script-targeted URLs. - "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignment only. - "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. - "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. - "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. - "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. + // Environments + "browser" : false, // Web Browser (window, document, etc) + "browserify" : true, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mocha" : true, // mocha + "mootools" : false, // MooTools + "node" : true, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface - // == Environments ==================================================== - // - // These options pre-define global variables that are exposed by - // popular JavaScript libraries and runtime environments—such as - // browser or node.js. - - "browser" : false, // Standard browser globals e.g. `window`, `document`. - "couch" : false, // Enable globals exposed by CouchDB. - "devel" : false, // Allow development statements e.g. `console.log();`. - "dojo" : false, // Enable globals exposed by Dojo Toolkit. - "jquery" : false, // Enable globals exposed by jQuery JavaScript library. - "mocha" : true, // Enable globals exposed by the mocha test runner. - "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. - "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. - "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. - "phantom" : false, // Enable globals exposed by PhantomJS. - "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. - "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. - "worker" : false, // Enable globals exposed when running inside a Web Worker. - "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. - "yui" : false, // Enable globals exposed by YUI. - - // == JSLint Legacy =================================================== - // - // These options are legacy from JSLint. Aside from bug fixes they will - // not be improved in any way and might be removed at any point. - - "nomen" : false, // Prohibit use of initial or trailing underbars in names. - "onevar" : false, // Allow only one `var` statement per function. - "passfail" : false, // Stop on first error. - "white" : false, // Check against strict whitespace and indentation rules. - - "globals": {} + // Custom Globals + "globals" : {} // additional predefined global variables } From dcef864c1cba95cfba865adac9651aee6c9dc5c6 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 28 Jul 2014 13:21:19 +0200 Subject: [PATCH 006/134] Fix URL if necessary. --- packages/xo-lib/index.js | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 4e4bbeaeb..02b0d77f4 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -4,7 +4,7 @@ var assign = require('lodash.assign'); var forEach = require('lodash.foreach'); - +var parseUrl = require('url').parse; var Promise = require('bluebird'); // Support browsers. @@ -21,10 +21,36 @@ var notConnected = function () { throw new Error('not connected'); }; +// Fix URL if necessary. +var fixUrl = function (url) { + url = parseUrl(url); + + // Add HTTP protocol if missing. + url.protocol || (url.protocol = 'http:'); + + // Suffix path with /api/ if missing. + var path = url.pathname; + if ('/' !== path[path.length - 1]) { + path += '/'; + } + if (!/\/api\/$/.test(path)) { + path += 'api/'; + } + + // Reconstruct the URL. + return [ + url.protocol, '//', + url.host, + path, + url.search, + url.hash, + ].join(''); +}; + //==================================================================== var Xo = function (url) { - this._url = url; + this._url = fixUrl(url); // Identifier of the next request. this._nextId = 0; @@ -153,4 +179,5 @@ assign(Xo.prototype, { //==================================================================== -module.exports = Xo; +exports = module.exports = Xo; +exports.fixUrl = fixUrl; From 42ec5095741edbea770918af5d76be88cca15df2 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 28 Jul 2014 13:30:57 +0200 Subject: [PATCH 007/134] Fix URL fixing. --- packages/xo-lib/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 02b0d77f4..72091bf5a 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -23,13 +23,15 @@ var notConnected = function () { // Fix URL if necessary. var fixUrl = function (url) { + // Add HTTP protocol if missing. + if (!/^https?:/.test(url)) { + url = 'http:'+ url; + } + url = parseUrl(url); - // Add HTTP protocol if missing. - url.protocol || (url.protocol = 'http:'); - // Suffix path with /api/ if missing. - var path = url.pathname; + var path = url.pathname || ''; if ('/' !== path[path.length - 1]) { path += '/'; } From 1d7d63965412e1ee50f8c9da37b3b4ab94594621 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 28 Jul 2014 13:33:29 +0200 Subject: [PATCH 008/134] Set rejectUnauthorized only for secure connection. --- packages/xo-lib/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 72091bf5a..a9f77254a 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -88,10 +88,12 @@ assign(Xo.prototype, { this.status = 'connecting'; - var socket = this._socket = new WebSocket(this._url, { + var opts = {}; + if (/^https/.test(this._url)) { // Due to imperfect TLS implementation in XO-Server. - rejectUnauthorized: false, - }); + opts.rejectUnauthorized = false; + } + var socket = this._socket = new WebSocket(this._url, opts); // When the socket opens, send any queued requests. socket.on('open', function () { From 09b39a4bf927d757d6de50cd2e9bf3690f4c1e61 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 28 Jul 2014 13:33:45 +0200 Subject: [PATCH 009/134] 0.2.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 4c863a0c0..7fcebc70e 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.1.1", + "version": "0.2.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From ebee1a02fd22a7f3fcd2feeb400fcbf454143802 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 11:46:56 +0100 Subject: [PATCH 010/134] Promise is already defined in ES6. --- packages/xo-lib/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index a9f77254a..5db09853a 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -5,7 +5,7 @@ var assign = require('lodash.assign'); var forEach = require('lodash.foreach'); var parseUrl = require('url').parse; -var Promise = require('bluebird'); +var Bluebird = require('bluebird'); // Support browsers. var WebSocket = (function (root) { @@ -81,10 +81,10 @@ assign(Xo.prototype, { connect: function () { if (this.status === 'connected') { - return Promise.cast(); + return Bluebird.cast(); } - var deferred = Promise.defer(); + var deferred = Bluebird.defer(); this.status = 'connecting'; @@ -174,7 +174,7 @@ assign(Xo.prototype, { params: params || [], })); - var deferred = this._deferreds[id] = Promise.defer(); + var deferred = this._deferreds[id] = Bluebird.defer(); return deferred.promise; }.bind(this)); From e9b0b0c42ec079664e0119a426d652ba04fe42e4 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 11:53:27 +0100 Subject: [PATCH 011/134] ws supports browser natively. --- packages/xo-lib/index.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 5db09853a..f46938795 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -3,17 +3,10 @@ //==================================================================== var assign = require('lodash.assign'); +var Bluebird = require('bluebird'); var forEach = require('lodash.foreach'); var parseUrl = require('url').parse; -var Bluebird = require('bluebird'); - -// Support browsers. -var WebSocket = (function (root) { - return (root && 'WebSocket' in root) ? - root.WebSocket : - require('ws') - ; -})(this); +var WebSocket = require('ws'); //==================================================================== From c6db974962b02edaef8840405c062421cbbfc0cd Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 11:55:00 +0100 Subject: [PATCH 012/134] Update jshintrc. --- packages/xo-lib/.jshintrc | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/xo-lib/.jshintrc b/packages/xo-lib/.jshintrc index 97fda4de7..9e30c8a86 100644 --- a/packages/xo-lib/.jshintrc +++ b/packages/xo-lib/.jshintrc @@ -1,14 +1,13 @@ { // 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 - // - allow expression statements (necessary for chai.expect()) - // - allow global strict (most of my devs are in Node.js or Browserify) - // - environments are set to Browserify, mocha & Node.js + // - environments are set to Node.js // // See http://jshint.com/docs/ for more details @@ -20,12 +19,14 @@ "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: @@ -35,50 +36,52 @@ // "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" : true, // true: Requires all functions run in ES5 Strict Mode - "maxparams" : 4, // {int} Max number of formal params allowed per function - "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) - "maxstatements" : 20, // {int} Max number statements per function + "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` - "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) - "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) - "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) - // (ex: `for each`, multiple try/catch, function expression…) + "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) "evil" : false, // true: Tolerate use of `eval` and `new Function()` - "expr" : true, // true: Tolerate `ExpressionStatement` as Programs + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs "funcscope" : false, // true: Tolerate defining variables inside control statements - "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "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" : true, // Browserify (node.js code in the browser) + "browserify" : false, // Browserify (node.js code in the browser) "couch" : false, // CouchDB - "devel" : true, // Development/debugging (alert, confirm, etc) + "devel" : false, // Development/debugging (alert, confirm, etc) "dojo" : false, // Dojo Toolkit "jquery" : false, // jQuery - "mocha" : true, // mocha + "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 From debcf086b5912b77d720def7d2068b9b3b195b16 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 11:57:05 +0100 Subject: [PATCH 013/134] Code style. --- packages/xo-lib/index.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index f46938795..c3e913a5d 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -10,12 +10,12 @@ var WebSocket = require('ws'); //==================================================================== -var notConnected = function () { +function notConnected() { throw new Error('not connected'); -}; +} // Fix URL if necessary. -var fixUrl = function (url) { +function fixUrl(url) { // Add HTTP protocol if missing. if (!/^https?:/.test(url)) { url = 'http:'+ url; @@ -40,11 +40,11 @@ var fixUrl = function (url) { url.search, url.hash, ].join(''); -}; +} //==================================================================== -var Xo = function (url) { +function Xo(url) { this._url = fixUrl(url); // Identifier of the next request. @@ -61,19 +61,17 @@ var Xo = function (url) { // - connecting // - connected this.status = 'disconnected'; -}; +} assign(Xo.prototype, { close: function () { - if (this._socket) - { + if (this._socket) { this._socket.close(); } }, connect: function () { - if (this.status === 'connected') - { + if (this.status === 'connected') { return Bluebird.cast(); } @@ -101,8 +99,7 @@ assign(Xo.prototype, { socket.on('message', function (data) { // `ws` API is lightly different from standard API. - if (data.data) - { + if (data.data) { data = data.data; } @@ -112,20 +109,17 @@ assign(Xo.prototype, { var id = response.id; var deferred = this._deferreds[id]; - if (!deferred) - { + if (!deferred) { // Response already handled. return; } delete this._deferreds[id]; - if ('error' in response) - { + if ('error' in response) { return deferred.reject(response.error); } - if ('result' in response) - { + if ('result' in response) { return deferred.resolve(response.result); } From f93f115e13528150cc2f2a56b8f0ffb090a7fcff Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 12:41:25 +0100 Subject: [PATCH 014/134] Better and tested fixUrl(). --- packages/xo-lib/index.js | 29 ++++++++------------------ packages/xo-lib/index.spec.js | 38 +++++++++++++++++++++++++++++++++++ packages/xo-lib/package.json | 6 +++++- 3 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 packages/xo-lib/index.spec.js diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index c3e913a5d..a3d7d6787 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -15,30 +15,17 @@ function notConnected() { } // Fix URL if necessary. +var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?$/; function fixUrl(url) { - // Add HTTP protocol if missing. - if (!/^https?:/.test(url)) { - url = 'http:'+ url; - } + var matches = URL_RE.exec(url); + var isSecure = !!matches[1]; + var rest = matches[2]; - url = parseUrl(url); - - // Suffix path with /api/ if missing. - var path = url.pathname || ''; - if ('/' !== path[path.length - 1]) { - path += '/'; - } - if (!/\/api\/$/.test(path)) { - path += 'api/'; - } - - // Reconstruct the URL. return [ - url.protocol, '//', - url.host, - path, - url.search, - url.hash, + isSecure ? 'wss' : 'ws', + '://', + rest, + '/api/', ].join(''); } diff --git a/packages/xo-lib/index.spec.js b/packages/xo-lib/index.spec.js new file mode 100644 index 000000000..c9cbb4537 --- /dev/null +++ b/packages/xo-lib/index.spec.js @@ -0,0 +1,38 @@ +'use strict'; + +//==================================================================== + +var expect = require('must'); + +//==================================================================== + +describe('fixUrl()', function () { + var fixUrl = require('./').fixUrl; + + describe('protocol', function () { + it('is added if missing', function () { + expect(fixUrl('localhost/api/')).to.equal('ws://localhost/api/'); + }); + + it('HTTP(s) is converted to WS(s)', function () { + expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/'); + expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/'); + }); + + it('is not added if already present', function () { + expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); + expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/'); + }); + }); + + describe('/api/ path', 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/'); + }); + + it('is not added if already present', function () { + expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); + }); + }); +}); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 7fcebc70e..c58d9ace6 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -19,7 +19,7 @@ "node": ">=0.8.0" }, "scripts": { - "test": "gulp test" + "test": "mocha index.spec.js" }, "files": [ "index.js" @@ -29,5 +29,9 @@ "lodash.assign": "^2.4.1", "lodash.foreach": "^2.4.1", "ws": "^0.4.31" + }, + "devDependencies": { + "mocha": "^2.1.0", + "must": "^0.12.0" } } From cecc4b1f6d7977ddeb180e446549a5b2aeeb249f Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:19:47 +0100 Subject: [PATCH 015/134] Fix secure URL check. --- packages/xo-lib/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index a3d7d6787..b8554abd3 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -14,6 +14,12 @@ function notConnected() { throw new Error('not connected'); } +function startsWith(string, target) { + return (string.lastIndexOf(target, 0) === 0); +} + +//==================================================================== + // Fix URL if necessary. var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?$/; function fixUrl(url) { @@ -67,7 +73,7 @@ assign(Xo.prototype, { this.status = 'connecting'; var opts = {}; - if (/^https/.test(this._url)) { + if (startsWith(this._url, 'wss')) { // Due to imperfect TLS implementation in XO-Server. opts.rejectUnauthorized = false; } From acdccd697c59c8ac68c2ee9d4c2fe13367366d1b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:22:49 +0100 Subject: [PATCH 016/134] Fix browser compatibility. --- packages/xo-lib/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index b8554abd3..956e8dd9c 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -77,10 +77,10 @@ assign(Xo.prototype, { // Due to imperfect TLS implementation in XO-Server. opts.rejectUnauthorized = false; } - var socket = this._socket = new WebSocket(this._url, opts); + var socket = this._socket = new WebSocket(this._url, '', opts); // When the socket opens, send any queued requests. - socket.on('open', function () { + socket.addEventListener('open', function () { this.status = 'connected'; // (Re)Opens accesses. @@ -90,7 +90,7 @@ assign(Xo.prototype, { deferred.resolve(); }.bind(this)); - socket.on('message', function (data) { + socket.addEventListener('message', function (data) { // `ws` API is lightly different from standard API. if (data.data) { data = data.data; @@ -122,7 +122,7 @@ assign(Xo.prototype, { }); }.bind(this)); - socket.on('close', function () { + socket.addEventListener('close', function () { // Closes accesses. this.send = notConnected; From 6acb87b7eabc946d663fe7cb36e7292844a3bbf2 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:23:26 +0100 Subject: [PATCH 017/134] Update Bluebird. --- packages/xo-lib/index.js | 29 +++++++++++++++++++++-------- packages/xo-lib/package.json | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 956e8dd9c..9ac42d614 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -10,6 +10,20 @@ var WebSocket = require('ws'); //==================================================================== +function makeDeferred() { + var resolve, reject; + var promise = new Bluebird(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + + return { + promise: promise, + reject: reject, + resolve: resolve, + }; +} + function notConnected() { throw new Error('not connected'); } @@ -63,15 +77,14 @@ assign(Xo.prototype, { } }, - connect: function () { + connect: Bluebird.method(function () { if (this.status === 'connected') { - return Bluebird.cast(); + return; } - - var deferred = Bluebird.defer(); - this.status = 'connecting'; + var deferred = makeDeferred(); + var opts = {}; if (startsWith(this._url, 'wss')) { // Due to imperfect TLS implementation in XO-Server. @@ -133,13 +146,13 @@ assign(Xo.prototype, { this._deferreds = {}; }.bind(this)); - socket.on('error', function (error) { + socket.addEventListener('error', function (error) { // Fails the connect promise if possible. deferred.reject(error); }); return deferred.promise; - }, + }), call: function (method, params) { return this.connect().then(function () { @@ -154,7 +167,7 @@ assign(Xo.prototype, { params: params || [], })); - var deferred = this._deferreds[id] = Bluebird.defer(); + var deferred = this._deferreds[id] = makeDeferred(); return deferred.promise; }.bind(this)); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index c58d9ace6..253d52b56 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -25,7 +25,7 @@ "index.js" ], "dependencies": { - "bluebird": "^2.2.2", + "bluebird": "^2.9.6", "lodash.assign": "^2.4.1", "lodash.foreach": "^2.4.1", "ws": "^0.4.31" From 216f8959534f11c88e35ac2b8c96bc77af80c890 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:23:56 +0100 Subject: [PATCH 018/134] Update deps. --- packages/xo-lib/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 253d52b56..533e84c97 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -26,9 +26,9 @@ ], "dependencies": { "bluebird": "^2.9.6", - "lodash.assign": "^2.4.1", - "lodash.foreach": "^2.4.1", - "ws": "^0.4.31" + "lodash.assign": "^3.0.0", + "lodash.foreach": "^3.0.1", + "ws": "^0.7.1" }, "devDependencies": { "mocha": "^2.1.0", From dd5270f620c11ca1ccd3e9faa5899daf8c273274 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:32:39 +0100 Subject: [PATCH 019/134] Minimalist browser example. --- packages/xo-lib/example/.gitignore | 2 ++ packages/xo-lib/example/README.md | 7 +++++++ packages/xo-lib/example/index.html | 9 +++++++++ packages/xo-lib/example/index.js | 13 +++++++++++++ packages/xo-lib/example/package.json | 11 +++++++++++ 5 files changed, 42 insertions(+) create mode 100644 packages/xo-lib/example/.gitignore create mode 100644 packages/xo-lib/example/README.md create mode 100644 packages/xo-lib/example/index.html create mode 100644 packages/xo-lib/example/index.js create mode 100644 packages/xo-lib/example/package.json diff --git a/packages/xo-lib/example/.gitignore b/packages/xo-lib/example/.gitignore new file mode 100644 index 000000000..52622bd07 --- /dev/null +++ b/packages/xo-lib/example/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +bundle.js diff --git a/packages/xo-lib/example/README.md b/packages/xo-lib/example/README.md new file mode 100644 index 000000000..432065770 --- /dev/null +++ b/packages/xo-lib/example/README.md @@ -0,0 +1,7 @@ +> Minimalist browser example. + +``` +> npm install +> npm run build +> open index.html +``` diff --git a/packages/xo-lib/example/index.html b/packages/xo-lib/example/index.html new file mode 100644 index 000000000..becd6365b --- /dev/null +++ b/packages/xo-lib/example/index.html @@ -0,0 +1,9 @@ + + + + xo-lib + + + + + diff --git a/packages/xo-lib/example/index.js b/packages/xo-lib/example/index.js new file mode 100644 index 000000000..5534d6524 --- /dev/null +++ b/packages/xo-lib/example/index.js @@ -0,0 +1,13 @@ +'use strict'; + +//==================================================================== + +var Xo = require('..'); + +//==================================================================== + +var xo = new Xo('localhost:9000'); + +xo.call('system.getMethodsInfo').then(function (methods) { + console.log(methods); +}); diff --git a/packages/xo-lib/example/package.json b/packages/xo-lib/example/package.json new file mode 100644 index 000000000..764b33d5e --- /dev/null +++ b/packages/xo-lib/example/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "scripts": { + "build": "browserify --outfile bundle.js index.js", + "dev": "watchify --debug --outfile bundle.js index.js" + }, + "devDependencies": { + "browserify": "^8.1.3", + "watchify": "^2.3.0" + } +} From 6d07d58f372b7beed1b9df6a1d47a56fe1396894 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:45:52 +0100 Subject: [PATCH 020/134] 0.3.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 533e84c97..d9764c060 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.2.0", + "version": "0.3.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 656d2494b0c49aca4effe9ca01a3dcdebd556a0f Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 13:51:50 +0100 Subject: [PATCH 021/134] Travis CI. --- packages/xo-lib/.travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/xo-lib/.travis.yml diff --git a/packages/xo-lib/.travis.yml b/packages/xo-lib/.travis.yml new file mode 100644 index 000000000..427dd41f8 --- /dev/null +++ b/packages/xo-lib/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - '0.10' + - '0.11' + - iojs From f598e0d0d5290ce466cb10058dcc5ae9a7a742f1 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 14:13:06 +0100 Subject: [PATCH 022/134] Not available via Bower. --- packages/xo-lib/README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 4f045eaec..7b0424d31 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -6,24 +6,23 @@ > Library to connect to XO-Server. +## Installation -## Install +### Node & Browserify -Download [manually](https://github.com/vatesfr/xo-lib/releases) or with package-manager. - -#### [npm](https://npmjs.org/package/xo-lib) +Installation of the [npm package](https://npmjs.org/package/xo-lib): ``` npm install --save xo-lib ``` -#### bower +Then require the package: -``` -bower install --save xo-lib +```javascript +var Xo = require('xo-lib'); ``` -## Example +## Usage ```javascript var Xo = require('xo-lib'); From fec8dd74afced2fcdb1b252624880245721f8665 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 5 Feb 2015 17:05:49 +0100 Subject: [PATCH 023/134] Use json-rpc. --- packages/xo-lib/index.js | 113 +++++++++++++---------------------- packages/xo-lib/package.json | 1 + 2 files changed, 42 insertions(+), 72 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 9ac42d614..93180d327 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -4,7 +4,11 @@ var assign = require('lodash.assign'); var Bluebird = require('bluebird'); +var EventEmitter = require('events').EventEmitter; var forEach = require('lodash.foreach'); +var inherits = require('util').inherits; +var jsonRpc = require('json-rpc'); +var MethodNotFound = require('json-rpc/errors').MethodNotFound; var parseUrl = require('url').parse; var WebSocket = require('ws'); @@ -24,10 +28,6 @@ function makeDeferred() { }; } -function notConnected() { - throw new Error('not connected'); -} - function startsWith(string, target) { return (string.lastIndexOf(target, 0) === 0); } @@ -52,23 +52,35 @@ function fixUrl(url) { //==================================================================== function Xo(url) { + // Super constructor. + EventEmitter.call(this); + + // Fix the URL (ensure correct protocol and /api/ path). this._url = fixUrl(url); - // Identifier of the next request. - this._nextId = 0; - - // Promises linked to the requests. - this._deferreds = {}; - - // Current WebSocket. - this._socket = null; - // Current status which may be: // - disconnected // - connecting // - connected this.status = 'disconnected'; + + // Will contains the WebSocket. + this._socket = null; + + // The JSON-RPC server. + var this_ = this; + this._jsonRpc = jsonRpc.createServer(function (message) { + if (message.type === 'notification') { + this_.emit('notification', message); + } else { + // This object does not support requests. + throw new MethodNotFound(message.method); + } + }).on('data', function (message) { + this_._socket.send(JSON.stringify(message)); + }); } +inherits(Xo, EventEmitter); assign(Xo.prototype, { close: function () { @@ -78,7 +90,7 @@ assign(Xo.prototype, { }, connect: Bluebird.method(function () { - if (this.status === 'connected') { + if (this._socket) { return; } this.status = 'connecting'; @@ -92,59 +104,27 @@ assign(Xo.prototype, { } var socket = this._socket = new WebSocket(this._url, '', opts); + // Used to avoid binding listeners to this object. + var this_ = this; + // When the socket opens, send any queued requests. socket.addEventListener('open', function () { this.status = 'connected'; - // (Re)Opens accesses. - delete this.send; - // Resolves the promise. deferred.resolve(); - }.bind(this)); + }); - socket.addEventListener('message', function (data) { - // `ws` API is lightly different from standard API. - if (data.data) { - data = data.data; - } - - // TODO: Wraps in a promise to prevent releasing the Zalgo. - var response = JSON.parse(data); - - var id = response.id; - - var deferred = this._deferreds[id]; - if (!deferred) { - // Response already handled. - return; - } - delete this._deferreds[id]; - - if ('error' in response) { - return deferred.reject(response.error); - } - - if ('result' in response) { - return deferred.resolve(response.result); - } - - deferred.reject({ - message: 'invalid response received', - object: response, - }); - }.bind(this)); + socket.addEventListener('message', function (message) { + this_._jsonRpc.write(message.data); + }); socket.addEventListener('close', function () { - // Closes accesses. - this.send = notConnected; + this_.status = 'disconnected'; + this_._socket = null; - // Fails all waiting requests. - forEach(this._deferreds, function (deferred) { - deferred.reject('not connected'); - }); - this._deferreds = {}; - }.bind(this)); + this_._jsonRpc.failPendingRequests('connection lost'); + }); socket.addEventListener('error', function (error) { // Fails the connect promise if possible. @@ -155,22 +135,11 @@ assign(Xo.prototype, { }), call: function (method, params) { + var jsonRpc = this._jsonRpc; + return this.connect().then(function () { - var socket = this._socket; - - var id = this._nextId++; - - socket.send(JSON.stringify({ - jsonrpc: '2.0', - id: id, - method: method, - params: params || [], - })); - - var deferred = this._deferreds[id] = makeDeferred(); - - return deferred.promise; - }.bind(this)); + return jsonRpc.request(method, params); + }); }, }); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index d9764c060..572239182 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -26,6 +26,7 @@ ], "dependencies": { "bluebird": "^2.9.6", + "json-rpc": "git://github.com/julien-f/js-json-rpc", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", "ws": "^0.7.1" From da99f3bc2a26ba7de22c7edd830fa20c6be33565 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 11:40:11 +0100 Subject: [PATCH 024/134] High level interface. --- packages/xo-lib/index.js | 136 ++++++++++++++++++++++++++++++----- packages/xo-lib/package.json | 2 +- 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 93180d327..b7f641699 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -5,11 +5,10 @@ var assign = require('lodash.assign'); var Bluebird = require('bluebird'); var EventEmitter = require('events').EventEmitter; -var forEach = require('lodash.foreach'); var inherits = require('util').inherits; var jsonRpc = require('json-rpc'); +var makeError = require('make-error'); var MethodNotFound = require('json-rpc/errors').MethodNotFound; -var parseUrl = require('url').parse; var WebSocket = require('ws'); //==================================================================== @@ -34,6 +33,40 @@ function startsWith(string, target) { //==================================================================== +function returnThis() { + /* jshint validthis: true */ + + return this; +} + +// Returns an iterator to the Fibonacci sequence. +function fibonacci(start) { + var prev = 0; + var curr = start || 1; + + var iterator = { + next: function () { + var tmp = curr; + curr += prev; + prev = tmp; + + return { + done: false, + value: prev, + }; + }, + }; + + // Make the iterator a true iterable (ES6). + if (typeof Symbol !== 'undefined') { + iterator[Symbol.iterator] = returnThis; + } + + return iterator; +} + +//==================================================================== + // Fix URL if necessary. var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?$/; function fixUrl(url) { @@ -48,22 +81,20 @@ function fixUrl(url) { '/api/', ].join(''); } +exports.fixUrl = fixUrl; //==================================================================== -function Xo(url) { +var ConnectionLost = makeError('ConnectionLost'); + +// Low level interface to XO. +function Api(url) { // Super constructor. EventEmitter.call(this); // Fix the URL (ensure correct protocol and /api/ path). this._url = fixUrl(url); - // Current status which may be: - // - disconnected - // - connecting - // - connected - this.status = 'disconnected'; - // Will contains the WebSocket. this._socket = null; @@ -80,9 +111,9 @@ function Xo(url) { this_._socket.send(JSON.stringify(message)); }); } -inherits(Xo, EventEmitter); +inherits(Api, EventEmitter); -assign(Xo.prototype, { +assign(Api.prototype, { close: function () { if (this._socket) { this._socket.close(); @@ -93,7 +124,6 @@ assign(Xo.prototype, { if (this._socket) { return; } - this.status = 'connecting'; var deferred = makeDeferred(); @@ -109,10 +139,10 @@ assign(Xo.prototype, { // When the socket opens, send any queued requests. socket.addEventListener('open', function () { - this.status = 'connected'; - // Resolves the promise. deferred.resolve(); + + this_.emit('connected'); }); socket.addEventListener('message', function (message) { @@ -120,10 +150,11 @@ assign(Xo.prototype, { }); socket.addEventListener('close', function () { - this_.status = 'disconnected'; this_._socket = null; - this_._jsonRpc.failPendingRequests('connection lost'); + this_._jsonRpc.failPendingRequests(new ConnectionLost()); + + this_.emit('disconnected'); }); socket.addEventListener('error', function (error) { @@ -143,7 +174,78 @@ assign(Xo.prototype, { }, }); +exports.Api = Api; + +//==================================================================== + + + +// High level interface to Xo. +// +// Handle auto-reconnect, sign in & objects cache. +function Xo(opts) { + var self = this; + + this._api = new Api(opts.url); + this._auth = opts.auth; + this._backOff = fibonacci(1e3); + this._objects = Object.create(null); + this.user = null; + + // Promise representing the connection status. + this._connection = null; + + this._onConnection = function () { + self._connection = self._api.call('session.signInWithPassword', { + email: self._auth.email, + password: self._auth.password, + }).then(function (user) { + this.user = user; + + return self._api.call('xo.getAllObjects'); + }).then(function (objects) { + self._objects = objects; + }); + }; + + self._api.on('disconnected', function () { + self._connection = null; + self._objects = Object.create(null); + }); + + self._api.on('notification', function (notification) { + if (notification.method !== 'all') { + return; + } + + + }); +} + +assign(Xo.prototype, { + connect: function () { + var self = this; + + return this._api.connect().then(this._onConnection).catch(function () { + return Bluebird.delay(self._backOff.next().value).then(function () { + return self.connect(); + }); + }); + }, + call: function (method, params) { + var self = this; + + return this._connect().then(function () { + return self._api.call(method, params).catch(ConnectionLost, function () { + // Retry automatically. + return self.call(method, params); + }); + }); + }, +}); + +exports.Xo = Xo; + //==================================================================== exports = module.exports = Xo; -exports.fixUrl = fixUrl; diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 572239182..f89fd4a8e 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -28,7 +28,7 @@ "bluebird": "^2.9.6", "json-rpc": "git://github.com/julien-f/js-json-rpc", "lodash.assign": "^3.0.0", - "lodash.foreach": "^3.0.1", + "make-error": "^0.3.0", "ws": "^0.7.1" }, "devDependencies": { From f1d359b3e7b13a3b5232d2d9905835ded98e7be3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 14:42:56 +0100 Subject: [PATCH 025/134] Smart objects collection. --- packages/xo-lib/collection.js | 141 ++++++++++++++++++++++++++++++++++ packages/xo-lib/index.js | 34 ++++++-- packages/xo-lib/package.json | 5 +- 3 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 packages/xo-lib/collection.js diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js new file mode 100644 index 000000000..ee43da093 --- /dev/null +++ b/packages/xo-lib/collection.js @@ -0,0 +1,141 @@ +'use strict'; + +//==================================================================== + +var forEach = require('lodash.foreach'); +var indexOf = require('lodash.indexof'); + +//==================================================================== + +function defaultKey(item) { + return item.id || item._id || item; +} + +//==================================================================== + +function Collection(opts) { + if (!opts) { + opts = {}; + } + + this._key = opts.key || defaultKey; + + this._indexes = Object.create(null); + if (opts.indexes) { + forEach(opts.indexes, function (field) { + this[field] = Object.create(null); + }, this._indexes); + } + + this._data = Object.create(null); +} + +function createIndex(_, field) { + /* jshint validthis: true */ + this[field] = Object.create(null); +} + +Collection.prototype.clear = function () { + this._data = Object.create(null); + forEach(this._indexes, createIndex, this._indexes); +}; + +Collection.prototype.get = function (key) { + return this._data[key]; +}; + +Collection.prototype.where = function (field, value) { + var index = this._indexes[field]; + + if (!index) { + throw new Error('no such index'); + } + + return index[value]; +}; + +function unsetItemFromIndex(index, field) { + /* jshint validthis: true */ + + var prop = this[field]; + if (!prop) { + return; + } + + var items = index[prop]; + + var i = indexOf(items, this); + if (i === -1) { + return; + } + + // The index contains only this one item for this prop. + if (items.length === 1) { + delete index[prop]; + return; + } + + // Remove this item. + items.splice(i, 1); +} + +// Internal unset method. +function unset(item, key) { + /* jshint validthis: true */ + + delete this._data[key]; + + forEach(this._indexes, unsetItemFromIndex, item); +} + +function setItemToIndex(index, field) { + /* jshint validthis: true */ + + var prop = this[field]; + if (!prop) { + return; + } + + var items = index[prop]; + if (items) { + // Update the items list. + items.push(this); + } else { + // Create the items list. + index[prop] = [this]; + } +} + +Collection.prototype.set = function (item) { + var key = this._key(item); + if (!key) { + // Ignore empty keys. + return; + } + + var previous = this._data[key]; + if (previous) { + unset.call(this, previous, key); + } + + this._data[key] = item; + forEach(this._indexes, setItemToIndex, item); +}; + +Collection.prototype.unset = function (item) { + unset.call(this, item, this._key(item)); +}; + +Collection.prototype.setMultiple = function (items) { + 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; diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index b7f641699..f1c81adbc 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -11,6 +11,8 @@ var makeError = require('make-error'); var MethodNotFound = require('json-rpc/errors').MethodNotFound; var WebSocket = require('ws'); +var createCollection = require('./collection'); + //==================================================================== function makeDeferred() { @@ -178,7 +180,16 @@ exports.Api = Api; //==================================================================== - +var objectsOptions = { + indexes: [ + 'ref', + 'type', + 'UUID', + ], + key: function (item) { + return item.UUID || item.ref; + }, +}; // High level interface to Xo. // @@ -189,7 +200,7 @@ function Xo(opts) { this._api = new Api(opts.url); this._auth = opts.auth; this._backOff = fibonacci(1e3); - this._objects = Object.create(null); + this.objects = createCollection(objectsOptions); this.user = null; // Promise representing the connection status. @@ -200,17 +211,19 @@ function Xo(opts) { email: self._auth.email, password: self._auth.password, }).then(function (user) { - this.user = user; + self.user = user; return self._api.call('xo.getAllObjects'); }).then(function (objects) { - self._objects = objects; + self.objects.setMultiple(objects); }); + + return self._connection; }; self._api.on('disconnected', function () { self._connection = null; - self._objects = Object.create(null); + self.objects.clear(); }); self._api.on('notification', function (notification) { @@ -218,14 +231,23 @@ function Xo(opts) { return; } + var method = ( + notification.params.type === 'exit' ? + 'unset' : + 'set' + ) + 'Multiple'; + self.objects[method](notification.params.items); }); } assign(Xo.prototype, { connect: function () { - var self = this; + if (this._connection) { + return this._connection; + } + var self = this; return this._api.connect().then(this._onConnection).catch(function () { return Bluebird.delay(self._backOff.next().value).then(function () { return self.connect(); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index f89fd4a8e..2fe2297e5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -22,12 +22,15 @@ "test": "mocha index.spec.js" }, "files": [ - "index.js" + "index.js", + "collection.js" ], "dependencies": { "bluebird": "^2.9.6", "json-rpc": "git://github.com/julien-f/js-json-rpc", "lodash.assign": "^3.0.0", + "lodash.foreach": "^3.0.1", + "lodash.indexof": "^3.0.0", "make-error": "^0.3.0", "ws": "^0.7.1" }, From ce53fe5e31241a73f72dc44c30b6e473a9807b77 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 14:47:16 +0100 Subject: [PATCH 026/134] Fix module exports. --- packages/xo-lib/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index f1c81adbc..be495714d 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -270,4 +270,8 @@ exports.Xo = Xo; //==================================================================== -exports = module.exports = Xo; +function createXo(opts) { + return new Xo(opts); +} + +exports = module.exports = assign(createXo, module.exports); From f2323a9d1983bdd29851ab4bdf711031a458e732 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 17:12:10 +0100 Subject: [PATCH 027/134] Various updates. --- packages/xo-lib/collection.js | 8 ++- packages/xo-lib/index.js | 96 ++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index ee43da093..dedbc1999 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -44,6 +44,12 @@ Collection.prototype.get = function (key) { return this._data[key]; }; +// Find the first entry in an index for a given value. +Collection.prototype.find = function (field, value) { + return this.where(field, value)[0]; +}; + +// Find all entries in an index for a given value. Collection.prototype.where = function (field, value) { var index = this._indexes[field]; @@ -51,7 +57,7 @@ Collection.prototype.where = function (field, value) { throw new Error('no such index'); } - return index[value]; + return index[value] || []; }; function unsetItemFromIndex(index, field) { diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index be495714d..837bb21a3 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -201,29 +201,16 @@ function Xo(opts) { this._auth = opts.auth; this._backOff = fibonacci(1e3); this.objects = createCollection(objectsOptions); + this.status = 'disconnected'; this.user = null; // Promise representing the connection status. this._connection = null; - this._onConnection = function () { - self._connection = self._api.call('session.signInWithPassword', { - email: self._auth.email, - password: self._auth.password, - }).then(function (user) { - self.user = user; - - return self._api.call('xo.getAllObjects'); - }).then(function (objects) { - self.objects.setMultiple(objects); - }); - - return self._connection; - }; - self._api.on('disconnected', function () { self._connection = null; self.objects.clear(); + self.status = 'disconnected'; }); self._api.on('notification', function (notification) { @@ -241,30 +228,67 @@ function Xo(opts) { }); } -assign(Xo.prototype, { - connect: function () { - if (this._connection) { - return this._connection; - } +function tryConnect() { + /* jshint validthis: true */ - var self = this; - return this._api.connect().then(this._onConnection).catch(function () { - return Bluebird.delay(self._backOff.next().value).then(function () { - return self.connect(); - }); - }); - }, - call: function (method, params) { - var self = this; + this.status = 'connecting'; + return this._api.connect().bind(this).catch(function () { + return Bluebird.delay(this._backOff.next().value).then(tryConnect); + }); +} - return this._connect().then(function () { - return self._api.call(method, params).catch(ConnectionLost, function () { - // Retry automatically. - return self.call(method, params); - }); +function onSuccessfulConnection() { + /* jshint validthis: true */ + + // FIXME: session.signIn() should work with both token and password. + return this._api.call( + this._auth.token ? + 'session.signInWithToken' : + 'session.signInWithPassword', + this._auth + ).bind(this).then(function (user) { + this.user = user; + this.status = 'connected'; + + this._api.call('xo.getAllObjects').bind(this).then(function (objects) { + this.objects.setMultiple(objects); }); - }, -}); + }); +} + +function onFailedConnection() { + /* jshint validthis: true */ + + this.status = 'disconnected'; +} + +Xo.prototype.connect = function () { + if (this._connection) { + return this._connection; + } + + this._connection = tryConnect.call(this).then( + onSuccessfulConnection, onFailedConnection + ); + return this._connection; +}; + +Xo.prototype.call = function (method, params) { + // TODO: prevent session.*() from being because it may interfere + // with this class session management. + + return this.connect().then(function () { + var self = this; + return this._api.call(method, params).catch(ConnectionLost, function () { + // Retry automatically. + return self.call(method, params); + }); + }); +}; + +Xo.prototype.close = function () { + this._api.close(); +}; exports.Xo = Xo; From e6ebc347e55985bd277fba19f013e510e482bcc3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 17:53:33 +0100 Subject: [PATCH 028/134] setScheduler() to ease integration. --- packages/xo-lib/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 837bb21a3..0495291e4 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -15,6 +15,11 @@ var createCollection = require('./collection'); //==================================================================== +// Expose Bluebird for now to ease integration (e.g. with Angular.js). +exports.setScheduler = Bluebird.setScheduler; + +//==================================================================== + function makeDeferred() { var resolve, reject; var promise = new Bluebird(function (resolve_, reject_) { From 0b17556fa4db80b3027114b006a70cdc5b8ea176 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 17:53:43 +0100 Subject: [PATCH 029/134] Auto reconnect. --- packages/xo-lib/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 0495291e4..9dfd54fa5 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -216,6 +216,9 @@ function Xo(opts) { self._connection = null; self.objects.clear(); self.status = 'disconnected'; + + // Automatically reconnect. + self.connect(); }); self._api.on('notification', function (notification) { From ae4af99c59afbd3900ae245890ba4575825f59c8 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 17:53:56 +0100 Subject: [PATCH 030/134] Xo cannot be closed. --- packages/xo-lib/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 9dfd54fa5..7a3111221 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -294,10 +294,6 @@ Xo.prototype.call = function (method, params) { }); }; -Xo.prototype.close = function () { - this._api.close(); -}; - exports.Xo = Xo; //==================================================================== From 0b4f808b2df2c2447494804c38536cf80c972e65 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:13:01 +0100 Subject: [PATCH 031/134] Only emit disconnected event if previously connected. --- packages/xo-lib/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 7a3111221..f0d5bb920 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -161,7 +161,10 @@ assign(Api.prototype, { this_._jsonRpc.failPendingRequests(new ConnectionLost()); - this_.emit('disconnected'); + // Only emit this event if connected before. + if (deferred.promise.isFulfilled()) { + this_.emit('disconnected'); + } }); socket.addEventListener('error', function (error) { From 1e1e079b655eeb511a415bdb9e9a20020bc966cd Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:21:07 +0100 Subject: [PATCH 032/134] Use current location if URL not provided (for browsers). --- packages/xo-lib/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index f0d5bb920..b50b1a1e5 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -92,6 +92,18 @@ exports.fixUrl = fixUrl; //==================================================================== +function getCurrentUrl() { + /* global window: false */ + + if (typeof window === undefined) { + throw new Error('cannot get current URL'); + } + + return window.location.host + window.location.pathname; +} + +//==================================================================== + var ConnectionLost = makeError('ConnectionLost'); // Low level interface to XO. @@ -100,7 +112,7 @@ function Api(url) { EventEmitter.call(this); // Fix the URL (ensure correct protocol and /api/ path). - this._url = fixUrl(url); + this._url = fixUrl(url || getCurrentUrl()); // Will contains the WebSocket. this._socket = null; From 74f7415f843579ce4d3d709fd726a0e5ee34f086 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:21:53 +0100 Subject: [PATCH 033/134] Minor changes. --- packages/xo-lib/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index b50b1a1e5..0ffae890b 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -254,9 +254,9 @@ function Xo(opts) { function tryConnect() { /* jshint validthis: true */ - this.status = 'connecting'; return this._api.connect().bind(this).catch(function () { - return Bluebird.delay(this._backOff.next().value).then(tryConnect); + var delay = this._backOff.next().value; + return Bluebird.delay(delay).bind(this).then(tryConnect); }); } @@ -290,6 +290,7 @@ Xo.prototype.connect = function () { return this._connection; } + this.status = 'connecting'; this._connection = tryConnect.call(this).then( onSuccessfulConnection, onFailedConnection ); From f62008aba433662cd6aa60418ed2ed3c6d494d6d Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:22:11 +0100 Subject: [PATCH 034/134] 0.4.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 2fe2297e5..eadf96874 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.3.0", + "version": "0.4.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 541a99bbc5f2af42ee65c852f95c1193d4a373f5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:56:32 +0100 Subject: [PATCH 035/134] Fix item removal. --- packages/xo-lib/collection.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index dedbc1999..ee8277f7c 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -129,6 +129,12 @@ Collection.prototype.set = function (item) { }; Collection.prototype.unset = function (item) { + var key = this._key(item); + item = this._data[key]; + if (!item) { + return; + } + unset.call(this, item, this._key(item)); }; From 4e0a3da01ede7c4cdd034822aee271a3c222e1ae Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 18:57:03 +0100 Subject: [PATCH 036/134] 0.4.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index eadf96874..b77a6594b 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.4.0", + "version": "0.4.1", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 5ae45ddd550762778cc0e40bc49032e67bc79de8 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 20:12:48 +0100 Subject: [PATCH 037/134] Do not clear objects on disconnect. --- packages/xo-lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 0ffae890b..e11cd06d1 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -229,7 +229,6 @@ function Xo(opts) { self._api.on('disconnected', function () { self._connection = null; - self.objects.clear(); self.status = 'disconnected'; // Automatically reconnect. @@ -274,6 +273,7 @@ function onSuccessfulConnection() { this.status = 'connected'; this._api.call('xo.getAllObjects').bind(this).then(function (objects) { + this.objects.clear(); this.objects.setMultiple(objects); }); }); From 85e2e14c818d6d5a7995d87918e267089bee7d9b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:11:10 +0100 Subject: [PATCH 038/134] README update. --- packages/xo-lib/README.md | 115 +++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 9 deletions(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 7b0424d31..0f735d982 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -19,25 +19,122 @@ npm install --save xo-lib Then require the package: ```javascript -var Xo = require('xo-lib'); +var xoLib = require('xo-lib'); ``` -## Usage +## High level API + +This high-level interface handles session sign-in and a cache of +remote XO objects. It also automatically reconnect and retry method +calls when necessary. ```javascript -var Xo = require('xo-lib'); +var xo = new xoLib.Xo({ + url: 'https://xo.company.tld', + auth: { + email: 'admin@admin.net', + password: 'admin', + }, +}); +``` -var xo = new Xo('https://xo.company.tld/api/'); +> If the URL is not provided and the current environment is a web +> browser, the location of the current page will be used. -xo.call('session.signInWithPassword', { +### Method call + +```javascript +xo.call('token.create').then(function (token) { + console.log('Token created', token); +}); +``` + +### Status + +The connection status is available through the status property which +is *disconnected*, *connecting* or *connected*. + +```javascript +console.log('%s to xo-server', xo.status); +``` + +### Current user + +Information about the user account used to sign in is available +through the `user` property. + +```javascript +console.log('Current user is', xo.user); +``` + +> This property is null when the status is not connected. + + +### XO Objects + +XO objects are cached locally in the `objects` collection. + +```javascript +// Get an object for a specific id. +var obj = xo.objects.get(id); + +// Get all VMs. +var vms = xo.objects.where('type', 'VM'); +``` + +## Low level + +```javascript +var api = new xoLib.Api('https://xo.company.tld'); +``` + +> If the URL is not provided and the current environment is a web +> browser, the location of the current page will be used. + +### Connection + +```javascript +api.connect().then(function () { + console.log('connected'); +}); +``` + +### Disconnection + +```javascript +api.close(); +``` + +### Method call + +```javascript +api.call('session.signInWithPassword', { email: 'admin@admin.net', password: 'admin', -}).then(function () { - return xo.call('session.getUser'); }).then(function (user) { - console.log(user); + console.log('Connected as', user); +}); +``` - xo.close(); +> A method call automatically trigger a connection if necessary. + +### Events + +```javascript +api.on('connected', function () { + console.log('connected'); +}); +``` + +```javascript +api.on('disconnected', function () { + console.log('disconnected'); +}); +``` + +```javascript +api.on('notification', function (notif) { + console.log('notification:', notif.method, notif.params); }); ``` From a8ca6b6fcbf6bf502a63c48c9039b6f01e1360a0 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:11:38 +0100 Subject: [PATCH 039/134] Do not use assign for methods. --- packages/xo-lib/index.js | 116 +++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index e11cd06d1..52787df32 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -132,70 +132,68 @@ function Api(url) { } inherits(Api, EventEmitter); -assign(Api.prototype, { - close: function () { - if (this._socket) { - this._socket.close(); +Api.prototype.close = function () { + if (this._socket) { + this._socket.close(); + } +}; + +Api.prototype.connect = Bluebird.method(function () { + if (this._socket) { + return; + } + + var deferred = makeDeferred(); + + var opts = {}; + if (startsWith(this._url, 'wss')) { + // Due to imperfect TLS implementation in XO-Server. + opts.rejectUnauthorized = false; + } + var socket = this._socket = new WebSocket(this._url, '', opts); + + // Used to avoid binding listeners to this object. + var this_ = this; + + // When the socket opens, send any queued requests. + socket.addEventListener('open', function () { + // Resolves the promise. + deferred.resolve(); + + this_.emit('connected'); + }); + + socket.addEventListener('message', function (message) { + this_._jsonRpc.write(message.data); + }); + + socket.addEventListener('close', function () { + this_._socket = null; + + this_._jsonRpc.failPendingRequests(new ConnectionLost()); + + // Only emit this event if connected before. + if (deferred.promise.isFulfilled()) { + this_.emit('disconnected'); } - }, + }); - connect: Bluebird.method(function () { - if (this._socket) { - return; - } + socket.addEventListener('error', function (error) { + // Fails the connect promise if possible. + deferred.reject(error); + }); - var deferred = makeDeferred(); - - var opts = {}; - if (startsWith(this._url, 'wss')) { - // Due to imperfect TLS implementation in XO-Server. - opts.rejectUnauthorized = false; - } - var socket = this._socket = new WebSocket(this._url, '', opts); - - // Used to avoid binding listeners to this object. - var this_ = this; - - // When the socket opens, send any queued requests. - socket.addEventListener('open', function () { - // Resolves the promise. - deferred.resolve(); - - this_.emit('connected'); - }); - - socket.addEventListener('message', function (message) { - this_._jsonRpc.write(message.data); - }); - - socket.addEventListener('close', function () { - this_._socket = null; - - this_._jsonRpc.failPendingRequests(new ConnectionLost()); - - // Only emit this event if connected before. - if (deferred.promise.isFulfilled()) { - this_.emit('disconnected'); - } - }); - - socket.addEventListener('error', function (error) { - // Fails the connect promise if possible. - deferred.reject(error); - }); - - return deferred.promise; - }), - - call: function (method, params) { - var jsonRpc = this._jsonRpc; - - return this.connect().then(function () { - return jsonRpc.request(method, params); - }); - }, + return deferred.promise; }); +Api.prototype.call = function (method, params) { + var jsonRpc = this._jsonRpc; + + return this.connect().then(function () { + return jsonRpc.request(method, params); + }); +}; + exports.Api = Api; //==================================================================== From 807da8f696c8da9df7e4ef5735a301ac2fbb0f88 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:13:01 +0100 Subject: [PATCH 040/134] Make Xo.connect() private. --- packages/xo-lib/index.js | 87 +++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 52787df32..21b5e8639 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -209,45 +209,6 @@ var objectsOptions = { }, }; -// High level interface to Xo. -// -// Handle auto-reconnect, sign in & objects cache. -function Xo(opts) { - var self = this; - - this._api = new Api(opts.url); - this._auth = opts.auth; - this._backOff = fibonacci(1e3); - this.objects = createCollection(objectsOptions); - this.status = 'disconnected'; - this.user = null; - - // Promise representing the connection status. - this._connection = null; - - self._api.on('disconnected', function () { - self._connection = null; - self.status = 'disconnected'; - - // Automatically reconnect. - self.connect(); - }); - - self._api.on('notification', function (notification) { - if (notification.method !== 'all') { - return; - } - - var method = ( - notification.params.type === 'exit' ? - 'unset' : - 'set' - ) + 'Multiple'; - - self.objects[method](notification.params.items); - }); -} - function tryConnect() { /* jshint validthis: true */ @@ -283,7 +244,9 @@ function onFailedConnection() { this.status = 'disconnected'; } -Xo.prototype.connect = function () { +function connect() { + /* jshint validthis: true */ + if (this._connection) { return this._connection; } @@ -293,13 +256,53 @@ Xo.prototype.connect = function () { onSuccessfulConnection, onFailedConnection ); return this._connection; -}; +} + +// High level interface to Xo. +// +// Handle auto-reconnect, sign in & objects cache. +function Xo(opts) { + var self = this; + + this._api = new Api(opts.url); + this._auth = opts.auth; + this._backOff = fibonacci(1e3); + this.objects = createCollection(objectsOptions); + this.status = 'disconnected'; + this.user = null; + + // Promise representing the connection status. + this._connection = null; + + self._api.on('disconnected', function () { + // Automatically reconnect. + self._connection = null; + connect.call(this); + }); + + self._api.on('notification', function (notification) { + if (notification.method !== 'all') { + return; + } + + var method = ( + notification.params.type === 'exit' ? + 'unset' : + 'set' + ) + 'Multiple'; + + self.objects[method](notification.params.items); + }); + + // Bootstrap the connection. + connect.call(this); +} Xo.prototype.call = function (method, params) { // TODO: prevent session.*() from being because it may interfere // with this class session management. - return this.connect().then(function () { + return connect.call(this).then(function () { var self = this; return this._api.call(method, params).catch(ConnectionLost, function () { // Retry automatically. From 097d195f0023c1fa4f7089d03cc597cb687eece5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:16:38 +0100 Subject: [PATCH 041/134] Disable session.*() from high level API. --- packages/xo-lib/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 21b5e8639..878bc686b 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -299,8 +299,11 @@ function Xo(opts) { } Xo.prototype.call = function (method, params) { - // TODO: prevent session.*() from being because it may interfere + // Prevent session.*() from being because it may interfere // with this class session management. + if (startsWith(method, 'session.')) { + throw new Error('session.*() methods are disabled from this interface'); + } return connect.call(this).then(function () { var self = this; From 72f8854a7a0c6e5f0b693d2cac5b8bb3d069000b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:39:52 +0100 Subject: [PATCH 042/134] Remove factory. --- packages/xo-lib/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 878bc686b..c3e04ef0e 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -315,11 +315,3 @@ Xo.prototype.call = function (method, params) { }; exports.Xo = Xo; - -//==================================================================== - -function createXo(opts) { - return new Xo(opts); -} - -exports = module.exports = assign(createXo, module.exports); From 1b720a504ca0cb43b56f38b65bf9892058734e34 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 14:40:19 +0100 Subject: [PATCH 043/134] 0.5.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index b77a6594b..beb489f9a 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.4.1", + "version": "0.5.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From c6d779853a50687180f9628119fb6a6a524d5b81 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 15:10:52 +0100 Subject: [PATCH 044/134] Remove unused require. --- packages/xo-lib/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index c3e04ef0e..1067a2566 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -2,7 +2,6 @@ //==================================================================== -var assign = require('lodash.assign'); var Bluebird = require('bluebird'); var EventEmitter = require('events').EventEmitter; var inherits = require('util').inherits; From c80f6e82858a27d070d481618e998bbda86b974e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 15:11:03 +0100 Subject: [PATCH 045/134] Better status during reconnection. --- packages/xo-lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 1067a2566..96360cafb 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -211,7 +211,9 @@ var objectsOptions = { function tryConnect() { /* jshint validthis: true */ + this.status = 'connecting'; return this._api.connect().bind(this).catch(function () { + this.status = 'disconnected'; var delay = this._backOff.next().value; return Bluebird.delay(delay).bind(this).then(tryConnect); }); @@ -250,7 +252,6 @@ function connect() { return this._connection; } - this.status = 'connecting'; this._connection = tryConnect.call(this).then( onSuccessfulConnection, onFailedConnection ); From a6db0f6fd93df9ebb42e9540b2cfad3be32f6837 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 15:11:23 +0100 Subject: [PATCH 046/134] Fix reconnection. --- packages/xo-lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 96360cafb..b434c415c 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -277,7 +277,7 @@ function Xo(opts) { self._api.on('disconnected', function () { // Automatically reconnect. self._connection = null; - connect.call(this); + connect.call(self); }); self._api.on('notification', function (notification) { From cd3c031df1957ad3e0f2d87956cf32e993ffb0f1 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 15:17:42 +0100 Subject: [PATCH 047/134] Fix back off reset. --- packages/xo-lib/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index b434c415c..18f26221b 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -222,6 +222,9 @@ function tryConnect() { function onSuccessfulConnection() { /* jshint validthis: true */ + // Reset back off. + this._backOff = fibonacci(1e3); + // FIXME: session.signIn() should work with both token and password. return this._api.call( this._auth.token ? From 28d5fb1822e233a12d92c722e59e26db1c89ff93 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 11 Feb 2015 15:17:51 +0100 Subject: [PATCH 048/134] 0.5.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index beb489f9a..bd05c25d5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.5.0", + "version": "0.5.1", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 21bd5ba3768bfd1618a5135a7357d00cb6ab2c62 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 12 Feb 2015 12:22:49 +0100 Subject: [PATCH 049/134] fixUrl() properly handles search and hash parts. --- packages/xo-lib/index.js | 8 +++++--- packages/xo-lib/index.spec.js | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 18f26221b..61894a8a4 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -74,17 +74,19 @@ function fibonacci(start) { //==================================================================== // Fix URL if necessary. -var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?$/; +var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/; function fixUrl(url) { var matches = URL_RE.exec(url); var isSecure = !!matches[1]; - var rest = matches[2]; + var hostAndPath = matches[2]; + var search = matches[3]; return [ isSecure ? 'wss' : 'ws', '://', - rest, + hostAndPath, '/api/', + search, ].join(''); } exports.fixUrl = fixUrl; diff --git a/packages/xo-lib/index.spec.js b/packages/xo-lib/index.spec.js index c9cbb4537..d00e88292 100644 --- a/packages/xo-lib/index.spec.js +++ b/packages/xo-lib/index.spec.js @@ -6,6 +6,8 @@ var expect = require('must'); //==================================================================== +/* jshint mocha: true */ + describe('fixUrl()', function () { var fixUrl = require('./').fixUrl; @@ -34,5 +36,13 @@ describe('fixUrl()', function () { it('is not added if already present', function () { expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); }); + + it('removes the hash part', function () { + expect(fixUrl('ws://localhost/#foo')).to.equal('ws://localhost/api/'); + }); + + it('conserve the search part', function () { + expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo'); + }); }); }); From 7e9639052b7ae0872b07d88405dcdfeb79b51680 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 12 Feb 2015 12:23:35 +0100 Subject: [PATCH 050/134] getCurrentUrl() must not ignore protocol and search parts. --- packages/xo-lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 61894a8a4..475a0624c 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -100,7 +100,7 @@ function getCurrentUrl() { throw new Error('cannot get current URL'); } - return window.location.host + window.location.pathname; + return String(window.location); } //==================================================================== From 6725cc6f611cacb0424f3f94575b48eaab1121fe Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 12 Feb 2015 12:23:56 +0100 Subject: [PATCH 051/134] 0.5.2 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index bd05c25d5..a959917a1 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.5.1", + "version": "0.5.2", "description": "Library to connect to XO-Server", "keywords": [ "xen", From a0a1353445f212ba3bb8f6d6a72839f834aa8e0c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 20 Feb 2015 17:14:44 +0100 Subject: [PATCH 052/134] Update collection API. --- packages/xo-lib/README.md | 19 ++++++++++++---- packages/xo-lib/collection.js | 42 ++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 0f735d982..6e29a1b3d 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -75,13 +75,24 @@ console.log('Current user is', xo.user); XO objects are cached locally in the `objects` collection. ```javascript -// Get an object for a specific id. -var obj = xo.objects.get(id); +// Read-only dictionary of all objects. +var allObjects = xo.objects.all; -// Get all VMs. -var vms = xo.objects.where('type', 'VM'); +// Looks up a given object by its identifier. +var object = allObjects[id]; + +// Read-only dictionary of all indexes. +var indexes = xo.objects.indexes; + +// Read-only dictionary of types. +var byTypes = indexes.type; + +// Read-only view of all VMs. +var vms = byTypes.VM; ``` +Available indexes are: `ref`, `type` and `UUID`. + ## Low level ```javascript diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index ee8277f7c..0cf1913ee 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -13,6 +13,16 @@ function defaultKey(item) { //==================================================================== +function getAll() { + /* jshint validthis: true */ + return this._all; +} + +function getIndexes() { + /* jshint validthis: true */ + return this._indexes; +} + function Collection(opts) { if (!opts) { opts = {}; @@ -28,6 +38,18 @@ function Collection(opts) { } this._data = Object.create(null); + + // Expose public properties. + Object.defineProperties(this, { + all: { + enumerable: true, + get: getAll, + }, + indexes: { + enumerable: true, + get: getIndexes, + }, + }); } function createIndex(_, field) { @@ -40,26 +62,6 @@ Collection.prototype.clear = function () { forEach(this._indexes, createIndex, this._indexes); }; -Collection.prototype.get = function (key) { - return this._data[key]; -}; - -// Find the first entry in an index for a given value. -Collection.prototype.find = function (field, value) { - return this.where(field, value)[0]; -}; - -// Find all entries in an index for a given value. -Collection.prototype.where = function (field, value) { - var index = this._indexes[field]; - - if (!index) { - throw new Error('no such index'); - } - - return index[value] || []; -}; - function unsetItemFromIndex(index, field) { /* jshint validthis: true */ From d0b37d0f9a4745a3c87ed3768d32f1623ee6bef5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 20 Feb 2015 17:15:00 +0100 Subject: [PATCH 053/134] Update example. --- packages/xo-lib/example/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/example/index.js b/packages/xo-lib/example/index.js index 5534d6524..3c5319d31 100644 --- a/packages/xo-lib/example/index.js +++ b/packages/xo-lib/example/index.js @@ -2,12 +2,12 @@ //==================================================================== -var Xo = require('..'); +var xoLib = require('..'); //==================================================================== -var xo = new Xo('localhost:9000'); +var api = new xoLib.Api('localhost:9000'); -xo.call('system.getMethodsInfo').then(function (methods) { +api.call('system.getMethodsInfo').then(function (methods) { console.log(methods); }); From 68e863723aff1a02d9b2ce6d74dc7656c655baa9 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:10:45 +0100 Subject: [PATCH 054/134] Explicitely handle sign in/out. --- packages/xo-lib/example.js | 34 ++++++++++ packages/xo-lib/index.js | 133 +++++++++++++++++++++++++------------ 2 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 packages/xo-lib/example.js diff --git a/packages/xo-lib/example.js b/packages/xo-lib/example.js new file mode 100644 index 000000000..f6fbedcec --- /dev/null +++ b/packages/xo-lib/example.js @@ -0,0 +1,34 @@ +var xoLib = require('./'); + +var xo = new xoLib.Xo({ + url: 'localhost:9000', +}); +xo.call('acl.get', {}).then(function (result) { + console.log('baz', result); +}).catch(function (error) { + console.log('error', error) +}); + +xo.signIn({ + email: 'admin@admin.net', + password: 'admin', +}).then(function () { + console.log('foo', xo.user); +}).catch(function (error) { + console.log('error', error) +}); + +xo.signIn({ + email: 'tom', + password: 'tom', +}).then(function () { + console.log('bar', xo.user); +}).catch(function (error) { + console.log('error', error) +}); + +xo.call('acl.get', {}).then(function (result) { + console.log('plop', result); +}).catch(function (error) { + console.log('error', error) +}) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 475a0624c..d42d31d7f 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -3,6 +3,7 @@ //==================================================================== var Bluebird = require('bluebird'); +Bluebird.longStackTraces(); var EventEmitter = require('events').EventEmitter; var inherits = require('util').inherits; var jsonRpc = require('json-rpc'); @@ -33,6 +34,19 @@ function makeDeferred() { }; } +function makeStandaloneDeferred() { + var resolve, reject; + + var promise = new Bluebird(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + promise.resolve = resolve; + promise.reject = reject; + + return promise; +} + function startsWith(string, target) { return (string.lastIndexOf(target, 0) === 0); } @@ -210,6 +224,7 @@ var objectsOptions = { }, }; +// Try connecting to Xo-Server. function tryConnect() { /* jshint validthis: true */ @@ -221,48 +236,55 @@ function tryConnect() { }); } -function onSuccessfulConnection() { +function resetSession() { /* jshint validthis: true */ - // Reset back off. - this._backOff = fibonacci(1e3); + // No session has been opened and no credentials has been provided + // yet: nothing to do. + if (this._credentials && this._credentials.isPending()) { + return; + } - // FIXME: session.signIn() should work with both token and password. - return this._api.call( - this._auth.token ? - 'session.signInWithToken' : - 'session.signInWithPassword', - this._auth - ).bind(this).then(function (user) { + // Clear any existing user. + this.user = null; + + // Create a promise for the next credentials. + this._credentials = makeStandaloneDeferred(); + + // The promise from the previous session needs to be rejected. + if (this._session && !this._session.isPending()) { + this._session.reject(); + } + + // Create a promise for the next session. + this._session = makeStandaloneDeferred(); +} + +function signIn() { + /* jshint validthis: true */ + + // Capture current session. + var session = this._session; + + this._credentials.bind(this).then(function (credentials) { + return this._api.call( + credentials.token ? + 'session.signInWithToken' : + 'session.signInWithPassword', + credentials + ); + }).then(function (user) { this.user = user; - this.status = 'connected'; this._api.call('xo.getAllObjects').bind(this).then(function (objects) { this.objects.clear(); this.objects.setMultiple(objects); }); + + session.resolve(); }); } -function onFailedConnection() { - /* jshint validthis: true */ - - this.status = 'disconnected'; -} - -function connect() { - /* jshint validthis: true */ - - if (this._connection) { - return this._connection; - } - - this._connection = tryConnect.call(this).then( - onSuccessfulConnection, onFailedConnection - ); - return this._connection; -} - // High level interface to Xo. // // Handle auto-reconnect, sign in & objects cache. @@ -270,19 +292,24 @@ function Xo(opts) { var self = this; this._api = new Api(opts.url); - this._auth = opts.auth; this._backOff = fibonacci(1e3); this.objects = createCollection(objectsOptions); this.status = 'disconnected'; - this.user = null; - // Promise representing the connection status. - this._connection = null; + self._api.on('connected', function () { + self.status = 'connected'; + + // Reset back off. + self._backOff = fibonacci(1e3); + + signIn.call(self); + }); self._api.on('disconnected', function () { - // Automatically reconnect. - self._connection = null; - connect.call(self); + self.status = 'disconnected'; + + resetSession.call(self); + tryConnect.call(self); }); self._api.on('notification', function (notification) { @@ -299,8 +326,8 @@ function Xo(opts) { self.objects[method](notification.params.items); }); - // Bootstrap the connection. - connect.call(this); + resetSession.call(this); + tryConnect.call(this); } Xo.prototype.call = function (method, params) { @@ -310,13 +337,33 @@ Xo.prototype.call = function (method, params) { throw new Error('session.*() methods are disabled from this interface'); } - return connect.call(this).then(function () { - var self = this; - return this._api.call(method, params).catch(ConnectionLost, function () { + return this._session.bind(this).then(function () { + return this._api.call(method, params).bind(this).catch(ConnectionLost, function () { // Retry automatically. - return self.call(method, params); + return this.call(method, params); }); }); }; +Xo.prototype.signIn = function (credentials) { + // Ignore the returned promise as it can cause concurrency issues. + this.signOut(); + + this._credentials.resolve(credentials); + + return this._session; +}; + +Xo.prototype.signOut = function () { + // Already signed in? + var promise; + if (!this._session.isPending()) { + promise = this._api.call('session.signOut'); + } + + resetSession.call(this); + + return promise || Bluebird.resolve(); +}; + exports.Xo = Xo; From 2fcb6d0c7cc0a078fadd65d76bc42b91112dfae0 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:11:53 +0100 Subject: [PATCH 055/134] 0.6.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index a959917a1..9e9563066 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.5.2", + "version": "0.6.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 12b42854e4c57856714179d9704ac511f227a310 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:23:42 +0100 Subject: [PATCH 056/134] Delete useless example. --- packages/xo-lib/example/.gitignore | 2 -- packages/xo-lib/example/README.md | 7 ------- packages/xo-lib/example/index.html | 9 --------- packages/xo-lib/example/index.js | 13 ------------- packages/xo-lib/example/package.json | 11 ----------- 5 files changed, 42 deletions(-) delete mode 100644 packages/xo-lib/example/.gitignore delete mode 100644 packages/xo-lib/example/README.md delete mode 100644 packages/xo-lib/example/index.html delete mode 100644 packages/xo-lib/example/index.js delete mode 100644 packages/xo-lib/example/package.json diff --git a/packages/xo-lib/example/.gitignore b/packages/xo-lib/example/.gitignore deleted file mode 100644 index 52622bd07..000000000 --- a/packages/xo-lib/example/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules/ -bundle.js diff --git a/packages/xo-lib/example/README.md b/packages/xo-lib/example/README.md deleted file mode 100644 index 432065770..000000000 --- a/packages/xo-lib/example/README.md +++ /dev/null @@ -1,7 +0,0 @@ -> Minimalist browser example. - -``` -> npm install -> npm run build -> open index.html -``` diff --git a/packages/xo-lib/example/index.html b/packages/xo-lib/example/index.html deleted file mode 100644 index becd6365b..000000000 --- a/packages/xo-lib/example/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - xo-lib - - - - - diff --git a/packages/xo-lib/example/index.js b/packages/xo-lib/example/index.js deleted file mode 100644 index 3c5319d31..000000000 --- a/packages/xo-lib/example/index.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -//==================================================================== - -var xoLib = require('..'); - -//==================================================================== - -var api = new xoLib.Api('localhost:9000'); - -api.call('system.getMethodsInfo').then(function (methods) { - console.log(methods); -}); diff --git a/packages/xo-lib/example/package.json b/packages/xo-lib/example/package.json deleted file mode 100644 index 764b33d5e..000000000 --- a/packages/xo-lib/example/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "private": true, - "scripts": { - "build": "browserify --outfile bundle.js index.js", - "dev": "watchify --debug --outfile bundle.js index.js" - }, - "devDependencies": { - "browserify": "^8.1.3", - "watchify": "^2.3.0" - } -} From 3dd0c4441091352b607ed37c7a480c9ae536137a Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:23:55 +0100 Subject: [PATCH 057/134] Handle string parameter in Xo constructor. --- packages/xo-lib/index.js | 9 +++++++++ packages/xo-lib/package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index d42d31d7f..2d66b1561 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -6,6 +6,7 @@ var Bluebird = require('bluebird'); Bluebird.longStackTraces(); var EventEmitter = require('events').EventEmitter; var inherits = require('util').inherits; +var isString = require('lodash.isstring'); var jsonRpc = require('json-rpc'); var makeError = require('make-error'); var MethodNotFound = require('json-rpc/errors').MethodNotFound; @@ -291,6 +292,14 @@ function signIn() { function Xo(opts) { var self = this; + if (!opts) { + opts = {}; + } else if (isString(opts)) { + opts = { + url: opts, + }; + } + this._api = new Api(opts.url); this._backOff = fibonacci(1e3); this.objects = createCollection(objectsOptions); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 9e9563066..02dc78166 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -31,6 +31,7 @@ "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", "lodash.indexof": "^3.0.0", + "lodash.isstring": "^3.0.0", "make-error": "^0.3.0", "ws": "^0.7.1" }, From fd066e5eefef25d8c54ace52a0c7481624475afe Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:25:17 +0100 Subject: [PATCH 058/134] Handle credentials directly in constructor. --- packages/xo-lib/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 2d66b1561..2c386924e 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -336,6 +336,11 @@ function Xo(opts) { }); resetSession.call(this); + + if (opts.credentials) { + this._credentials.resolve(opts.credentials); + } + tryConnect.call(this); } From 6e42a67268fd467ef37f9b62513fa39998232bbd Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:25:30 +0100 Subject: [PATCH 059/134] Update README. --- packages/xo-lib/README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 6e29a1b3d..789014e37 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -28,13 +28,29 @@ This high-level interface handles session sign-in and a cache of remote XO objects. It also automatically reconnect and retry method calls when necessary. +```javascript +// Connect to XO. +var xo = new xoLib.Xo('https://xo.company.tld'); + +// Must sign in before being able to call any methods (all calls will +// be buffered until signed in). +xo.signIn({ + email: 'admin@admin.net', + password: 'admin', +}).then(function () { + console('signed as', xo.user); +}); +``` + +The credentials can also be passed directly to the constructor: + ```javascript var xo = new xoLib.Xo({ url: 'https://xo.company.tld', - auth: { + credentials: { email: 'admin@admin.net', password: 'admin', - }, + } }); ``` From db4d6511d696d78a20f45bf291c54e7349aa777a Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 12:25:53 +0100 Subject: [PATCH 060/134] 0.6.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 02dc78166..845ce8d13 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.6.0", + "version": "0.6.1", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 3c3ea0f3e1384c8620333d27f15ee96bbcac2abd Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 14:47:11 +0100 Subject: [PATCH 061/134] Fix Collection::data. --- packages/xo-lib/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index 0cf1913ee..d49eb0ea1 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -15,7 +15,7 @@ function defaultKey(item) { function getAll() { /* jshint validthis: true */ - return this._all; + return this._data; } function getIndexes() { From 06b71166923e71f067458ab30efb1b5a51a467b9 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 14:48:32 +0100 Subject: [PATCH 062/134] Never break object refs for Collection:all & Collection:indexes[*]. --- packages/xo-lib/collection.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index d49eb0ea1..5340c5c82 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -7,6 +7,16 @@ var indexOf = require('lodash.indexof'); //==================================================================== +function deleteProperties(obj) { + /* jshint forin: false */ + var prop; + for (prop in obj) { + delete obj[prop]; + } +} + +//==================================================================== + function defaultKey(item) { return item.id || item._id || item; } @@ -52,14 +62,9 @@ function Collection(opts) { }); } -function createIndex(_, field) { - /* jshint validthis: true */ - this[field] = Object.create(null); -} - Collection.prototype.clear = function () { - this._data = Object.create(null); - forEach(this._indexes, createIndex, this._indexes); + deleteProperties(this._data); + forEach(this._indexes, deleteProperties); }; function unsetItemFromIndex(index, field) { From 6ff17d16f00e231237c6badc2c0d24db9bf618ec Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 23 Feb 2015 14:48:39 +0100 Subject: [PATCH 063/134] 0.6.2 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 845ce8d13..9c80456ae 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.6.1", + "version": "0.6.2", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 25472bcfa6b943207f28b0feb3a1e15cc6164e63 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 15:01:03 +0100 Subject: [PATCH 064/134] Correctly catch some errors. --- packages/xo-lib/index.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 2c386924e..725c60007 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -48,6 +48,8 @@ function makeStandaloneDeferred() { return promise; } +function noop() {} + function startsWith(string, target) { return (string.lastIndexOf(target, 0) === 0); } @@ -253,7 +255,10 @@ function resetSession() { this._credentials = makeStandaloneDeferred(); // The promise from the previous session needs to be rejected. - if (this._session && !this._session.isPending()) { + if (this._session && this._session.isPending()) { + // Ensure Bluebird does not mark this rejection as unhandled. + this._session.catch(noop); + this._session.reject(); } @@ -274,16 +279,21 @@ function signIn() { 'session.signInWithPassword', credentials ); - }).then(function (user) { - this.user = user; + }).then( + function (user) { + this.user = user; - this._api.call('xo.getAllObjects').bind(this).then(function (objects) { - this.objects.clear(); - this.objects.setMultiple(objects); - }); + this._api.call('xo.getAllObjects').bind(this).then(function (objects) { + this.objects.clear(); + this.objects.setMultiple(objects); + }).catch(noop); // Ignore any errors. - session.resolve(); - }); + session.resolve(); + }, + function (error) { + session.reject(error); + } + ); } // High level interface to Xo. @@ -377,6 +387,8 @@ Xo.prototype.signOut = function () { resetSession.call(this); + signIn.call(this); + return promise || Bluebird.resolve(); }; From dda51f2801231227b106b71389ace5147b978505 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 15:01:30 +0100 Subject: [PATCH 065/134] Xo::call() should never through synchronously. --- packages/xo-lib/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 725c60007..abec8f8b7 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -358,7 +358,9 @@ Xo.prototype.call = function (method, params) { // Prevent session.*() from being because it may interfere // with this class session management. if (startsWith(method, 'session.')) { - throw new Error('session.*() methods are disabled from this interface'); + return Bluebird.reject( + new Error('session.*() methods are disabled from this interface') + ); } return this._session.bind(this).then(function () { From 07da03618fa468529f1e9fbaf3cac187cf10231b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 15:51:35 +0100 Subject: [PATCH 066/134] Use specific version of json-rpc. --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 9c80456ae..81abd6ec8 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -27,7 +27,7 @@ ], "dependencies": { "bluebird": "^2.9.6", - "json-rpc": "git://github.com/julien-f/js-json-rpc", + "json-rpc": "git://github.com/julien-f/js-json-rpc#v0.3.1", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", "lodash.indexof": "^3.0.0", From daf42b63c8cad1469b29f22901be8d37c1c8219b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 16:18:57 +0100 Subject: [PATCH 067/134] Better back off implementation. --- packages/xo-lib/index.js | 41 ++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index abec8f8b7..ca9ef2d4e 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -88,6 +88,35 @@ function fibonacci(start) { return iterator; } +//-------------------------------------------------------------------- + +function BackOff(generator, opts) { + if (!opts) { + opts = {}; + } + + this._attempts = 0; + this._generator = generator; + this._iterator = generator(); + this._maxAttempts = opts.maxAttempts || Infinity; +} + +BackOff.prototype.wait = function () { + var maxAttempts = this._maxAttempts; + if (this._attempts++ > maxAttempts) { + return Bluebird.reject(new Error( + 'maximum attempts reached (' + maxAttempts +')' + )); + } + + return Bluebird.delay(this._iterator.next().value); +}; + +BackOff.prototype.reset = function () { + this._attempts = 0; + this._iterator = this._generator(); +}; + //==================================================================== // Fix URL if necessary. @@ -234,8 +263,8 @@ function tryConnect() { this.status = 'connecting'; return this._api.connect().bind(this).catch(function () { this.status = 'disconnected'; - var delay = this._backOff.next().value; - return Bluebird.delay(delay).bind(this).then(tryConnect); + + return this._backOff.wait().bind(this).then(tryConnect); }); } @@ -311,15 +340,15 @@ function Xo(opts) { } this._api = new Api(opts.url); - this._backOff = fibonacci(1e3); + this._backOff = new BackOff(function () { + return fibonacci(1e3); + }); this.objects = createCollection(objectsOptions); this.status = 'disconnected'; self._api.on('connected', function () { self.status = 'connected'; - - // Reset back off. - self._backOff = fibonacci(1e3); + self._backOff.reset(); signIn.call(self); }); From 292c9291174d63c7ac4fb697bdcb559ea71ca7a3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 17:31:14 +0100 Subject: [PATCH 068/134] Split code in to multiple files. --- packages/xo-lib/.npmignore | 1 + packages/xo-lib/api.js | 132 ++++++ packages/xo-lib/connection-error.js | 9 + packages/xo-lib/fix-url.js | 21 + .../xo-lib/{index.spec.js => fix-url.spec.js} | 2 +- packages/xo-lib/index.js | 426 +----------------- packages/xo-lib/package.json | 7 +- packages/xo-lib/xo.js | 278 ++++++++++++ 8 files changed, 446 insertions(+), 430 deletions(-) create mode 100644 packages/xo-lib/.npmignore create mode 100644 packages/xo-lib/api.js create mode 100644 packages/xo-lib/connection-error.js create mode 100644 packages/xo-lib/fix-url.js rename packages/xo-lib/{index.spec.js => fix-url.spec.js} (97%) create mode 100644 packages/xo-lib/xo.js diff --git a/packages/xo-lib/.npmignore b/packages/xo-lib/.npmignore new file mode 100644 index 000000000..889f827ca --- /dev/null +++ b/packages/xo-lib/.npmignore @@ -0,0 +1 @@ +*.spec.js diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js new file mode 100644 index 000000000..32dbb20a1 --- /dev/null +++ b/packages/xo-lib/api.js @@ -0,0 +1,132 @@ +'use strict'; + +//==================================================================== + +var Bluebird = require('bluebird'); +var EventEmitter = require('events').EventEmitter; +var inherits = require('util').inherits; +var jsonRpc = require('json-rpc'); +var MethodNotFound = require('json-rpc/errors').MethodNotFound; +var startsWith = require('lodash.startsWith'); +var WebSocket = require('ws'); + +var ConnectionError = require('./connection-error'); +var fixUrl = require('./fixUrl'); + +//==================================================================== + +function getCurrentUrl() { + /* global window: false */ + + if (typeof window === undefined) { + throw new Error('cannot get current URL'); + } + + return String(window.location); +} + +function makeDeferred() { + var resolve, reject; + var promise = new Bluebird(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + + return { + promise: promise, + reject: reject, + resolve: resolve, + }; +} + +//-------------------------------------------------------------------- + +// Low level interface to XO. +function Api(url) { + // Super constructor. + EventEmitter.call(this); + + // Fix the URL (ensure correct protocol and /api/ path). + this._url = fixUrl(url || getCurrentUrl()); + + // Will contains the WebSocket. + this._socket = null; + + // The JSON-RPC server. + var this_ = this; + this._jsonRpc = jsonRpc.createServer(function (message) { + if (message.type === 'notification') { + this_.emit('notification', message); + } else { + // This object does not support requests. + throw new MethodNotFound(message.method); + } + }).on('data', function (message) { + this_._socket.send(JSON.stringify(message)); + }); +} +inherits(Api, EventEmitter); + +Api.prototype.close = function () { + if (this._socket) { + this._socket.close(); + } +}; + +Api.prototype.connect = Bluebird.method(function () { + if (this._socket) { + return; + } + + var deferred = makeDeferred(); + + var opts = {}; + if (startsWith(this._url, 'wss')) { + // Due to imperfect TLS implementation in XO-Server. + opts.rejectUnauthorized = false; + } + var socket = this._socket = new WebSocket(this._url, '', opts); + + // Used to avoid binding listeners to this object. + var this_ = this; + + // When the socket opens, send any queued requests. + socket.addEventListener('open', function () { + // Resolves the promise. + deferred.resolve(); + + this_.emit('connected'); + }); + + socket.addEventListener('message', function (message) { + this_._jsonRpc.write(message.data); + }); + + socket.addEventListener('close', function () { + this_._socket = null; + + this_._jsonRpc.failPendingRequests(new ConnectionError()); + + // Only emit this event if connected before. + if (deferred.promise.isFulfilled()) { + this_.emit('disconnected'); + } + }); + + socket.addEventListener('error', function (error) { + // Fails the connect promise if possible. + deferred.reject(error); + }); + + return deferred.promise; +}); + +Api.prototype.call = function (method, params) { + var jsonRpc = this._jsonRpc; + + return this.connect().then(function () { + return jsonRpc.request(method, params); + }); +}; + +module.exports = Api; diff --git a/packages/xo-lib/connection-error.js b/packages/xo-lib/connection-error.js new file mode 100644 index 000000000..79f959f7f --- /dev/null +++ b/packages/xo-lib/connection-error.js @@ -0,0 +1,9 @@ +'use strict'; + +//==================================================================== + +var makeError = require('make-error'); + +//==================================================================== + +module.exports = makeError('ConnectionError'); diff --git a/packages/xo-lib/fix-url.js b/packages/xo-lib/fix-url.js new file mode 100644 index 000000000..6ed8c5bae --- /dev/null +++ b/packages/xo-lib/fix-url.js @@ -0,0 +1,21 @@ +'use strict'; + +//==================================================================== + +// Fix URL if necessary. +var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/; +function fixUrl(url) { + var matches = URL_RE.exec(url); + var isSecure = !!matches[1]; + var hostAndPath = matches[2]; + var search = matches[3]; + + return [ + isSecure ? 'wss' : 'ws', + '://', + hostAndPath, + '/api/', + search, + ].join(''); +} +module.exports = fixUrl; diff --git a/packages/xo-lib/index.spec.js b/packages/xo-lib/fix-url.spec.js similarity index 97% rename from packages/xo-lib/index.spec.js rename to packages/xo-lib/fix-url.spec.js index d00e88292..7ab89d920 100644 --- a/packages/xo-lib/index.spec.js +++ b/packages/xo-lib/fix-url.spec.js @@ -9,7 +9,7 @@ var expect = require('must'); /* jshint mocha: true */ describe('fixUrl()', function () { - var fixUrl = require('./').fixUrl; + var fixUrl = require('./fix-url'); describe('protocol', function () { it('is added if missing', function () { diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index ca9ef2d4e..7b55b18c4 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -1,426 +1,4 @@ 'use strict'; -//==================================================================== - -var Bluebird = require('bluebird'); -Bluebird.longStackTraces(); -var EventEmitter = require('events').EventEmitter; -var inherits = require('util').inherits; -var isString = require('lodash.isstring'); -var jsonRpc = require('json-rpc'); -var makeError = require('make-error'); -var MethodNotFound = require('json-rpc/errors').MethodNotFound; -var WebSocket = require('ws'); - -var createCollection = require('./collection'); - -//==================================================================== - -// Expose Bluebird for now to ease integration (e.g. with Angular.js). -exports.setScheduler = Bluebird.setScheduler; - -//==================================================================== - -function makeDeferred() { - var resolve, reject; - var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); - - return { - promise: promise, - reject: reject, - resolve: resolve, - }; -} - -function makeStandaloneDeferred() { - var resolve, reject; - - var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); - promise.resolve = resolve; - promise.reject = reject; - - return promise; -} - -function noop() {} - -function startsWith(string, target) { - return (string.lastIndexOf(target, 0) === 0); -} - -//==================================================================== - -function returnThis() { - /* jshint validthis: true */ - - return this; -} - -// Returns an iterator to the Fibonacci sequence. -function fibonacci(start) { - var prev = 0; - var curr = start || 1; - - var iterator = { - next: function () { - var tmp = curr; - curr += prev; - prev = tmp; - - return { - done: false, - value: prev, - }; - }, - }; - - // Make the iterator a true iterable (ES6). - if (typeof Symbol !== 'undefined') { - iterator[Symbol.iterator] = returnThis; - } - - return iterator; -} - -//-------------------------------------------------------------------- - -function BackOff(generator, opts) { - if (!opts) { - opts = {}; - } - - this._attempts = 0; - this._generator = generator; - this._iterator = generator(); - this._maxAttempts = opts.maxAttempts || Infinity; -} - -BackOff.prototype.wait = function () { - var maxAttempts = this._maxAttempts; - if (this._attempts++ > maxAttempts) { - return Bluebird.reject(new Error( - 'maximum attempts reached (' + maxAttempts +')' - )); - } - - return Bluebird.delay(this._iterator.next().value); -}; - -BackOff.prototype.reset = function () { - this._attempts = 0; - this._iterator = this._generator(); -}; - -//==================================================================== - -// Fix URL if necessary. -var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/; -function fixUrl(url) { - var matches = URL_RE.exec(url); - var isSecure = !!matches[1]; - var hostAndPath = matches[2]; - var search = matches[3]; - - return [ - isSecure ? 'wss' : 'ws', - '://', - hostAndPath, - '/api/', - search, - ].join(''); -} -exports.fixUrl = fixUrl; - -//==================================================================== - -function getCurrentUrl() { - /* global window: false */ - - if (typeof window === undefined) { - throw new Error('cannot get current URL'); - } - - return String(window.location); -} - -//==================================================================== - -var ConnectionLost = makeError('ConnectionLost'); - -// Low level interface to XO. -function Api(url) { - // Super constructor. - EventEmitter.call(this); - - // Fix the URL (ensure correct protocol and /api/ path). - this._url = fixUrl(url || getCurrentUrl()); - - // Will contains the WebSocket. - this._socket = null; - - // The JSON-RPC server. - var this_ = this; - this._jsonRpc = jsonRpc.createServer(function (message) { - if (message.type === 'notification') { - this_.emit('notification', message); - } else { - // This object does not support requests. - throw new MethodNotFound(message.method); - } - }).on('data', function (message) { - this_._socket.send(JSON.stringify(message)); - }); -} -inherits(Api, EventEmitter); - -Api.prototype.close = function () { - if (this._socket) { - this._socket.close(); - } -}; - -Api.prototype.connect = Bluebird.method(function () { - if (this._socket) { - return; - } - - var deferred = makeDeferred(); - - var opts = {}; - if (startsWith(this._url, 'wss')) { - // Due to imperfect TLS implementation in XO-Server. - opts.rejectUnauthorized = false; - } - var socket = this._socket = new WebSocket(this._url, '', opts); - - // Used to avoid binding listeners to this object. - var this_ = this; - - // When the socket opens, send any queued requests. - socket.addEventListener('open', function () { - // Resolves the promise. - deferred.resolve(); - - this_.emit('connected'); - }); - - socket.addEventListener('message', function (message) { - this_._jsonRpc.write(message.data); - }); - - socket.addEventListener('close', function () { - this_._socket = null; - - this_._jsonRpc.failPendingRequests(new ConnectionLost()); - - // Only emit this event if connected before. - if (deferred.promise.isFulfilled()) { - this_.emit('disconnected'); - } - }); - - socket.addEventListener('error', function (error) { - // Fails the connect promise if possible. - deferred.reject(error); - }); - - return deferred.promise; -}); - -Api.prototype.call = function (method, params) { - var jsonRpc = this._jsonRpc; - - return this.connect().then(function () { - return jsonRpc.request(method, params); - }); -}; - -exports.Api = Api; - -//==================================================================== - -var objectsOptions = { - indexes: [ - 'ref', - 'type', - 'UUID', - ], - key: function (item) { - return item.UUID || item.ref; - }, -}; - -// Try connecting to Xo-Server. -function tryConnect() { - /* jshint validthis: true */ - - this.status = 'connecting'; - return this._api.connect().bind(this).catch(function () { - this.status = 'disconnected'; - - return this._backOff.wait().bind(this).then(tryConnect); - }); -} - -function resetSession() { - /* jshint validthis: true */ - - // No session has been opened and no credentials has been provided - // yet: nothing to do. - if (this._credentials && this._credentials.isPending()) { - return; - } - - // Clear any existing user. - this.user = null; - - // Create a promise for the next credentials. - this._credentials = makeStandaloneDeferred(); - - // The promise from the previous session needs to be rejected. - if (this._session && this._session.isPending()) { - // Ensure Bluebird does not mark this rejection as unhandled. - this._session.catch(noop); - - this._session.reject(); - } - - // Create a promise for the next session. - this._session = makeStandaloneDeferred(); -} - -function signIn() { - /* jshint validthis: true */ - - // Capture current session. - var session = this._session; - - this._credentials.bind(this).then(function (credentials) { - return this._api.call( - credentials.token ? - 'session.signInWithToken' : - 'session.signInWithPassword', - credentials - ); - }).then( - function (user) { - this.user = user; - - this._api.call('xo.getAllObjects').bind(this).then(function (objects) { - this.objects.clear(); - this.objects.setMultiple(objects); - }).catch(noop); // Ignore any errors. - - session.resolve(); - }, - function (error) { - session.reject(error); - } - ); -} - -// High level interface to Xo. -// -// Handle auto-reconnect, sign in & objects cache. -function Xo(opts) { - var self = this; - - if (!opts) { - opts = {}; - } else if (isString(opts)) { - opts = { - url: opts, - }; - } - - this._api = new Api(opts.url); - this._backOff = new BackOff(function () { - return fibonacci(1e3); - }); - this.objects = createCollection(objectsOptions); - this.status = 'disconnected'; - - self._api.on('connected', function () { - self.status = 'connected'; - self._backOff.reset(); - - signIn.call(self); - }); - - self._api.on('disconnected', function () { - self.status = 'disconnected'; - - resetSession.call(self); - tryConnect.call(self); - }); - - self._api.on('notification', function (notification) { - if (notification.method !== 'all') { - return; - } - - var method = ( - notification.params.type === 'exit' ? - 'unset' : - 'set' - ) + 'Multiple'; - - self.objects[method](notification.params.items); - }); - - resetSession.call(this); - - if (opts.credentials) { - this._credentials.resolve(opts.credentials); - } - - tryConnect.call(this); -} - -Xo.prototype.call = function (method, params) { - // Prevent session.*() from being because it may interfere - // with this class session management. - if (startsWith(method, 'session.')) { - return Bluebird.reject( - new Error('session.*() methods are disabled from this interface') - ); - } - - return this._session.bind(this).then(function () { - return this._api.call(method, params).bind(this).catch(ConnectionLost, function () { - // Retry automatically. - return this.call(method, params); - }); - }); -}; - -Xo.prototype.signIn = function (credentials) { - // Ignore the returned promise as it can cause concurrency issues. - this.signOut(); - - this._credentials.resolve(credentials); - - return this._session; -}; - -Xo.prototype.signOut = function () { - // Already signed in? - var promise; - if (!this._session.isPending()) { - promise = this._api.call('session.signOut'); - } - - resetSession.call(this); - - signIn.call(this); - - return promise || Bluebird.resolve(); -}; - -exports.Xo = Xo; +exports.Api = require('./api'); +exports.Xo = require('./xo'); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 81abd6ec8..3e5d4c54f 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -19,12 +19,8 @@ "node": ">=0.8.0" }, "scripts": { - "test": "mocha index.spec.js" + "test": "mocha *.spec.js" }, - "files": [ - "index.js", - "collection.js" - ], "dependencies": { "bluebird": "^2.9.6", "json-rpc": "git://github.com/julien-f/js-json-rpc#v0.3.1", @@ -32,6 +28,7 @@ "lodash.foreach": "^3.0.1", "lodash.indexof": "^3.0.0", "lodash.isstring": "^3.0.0", + "lodash.startswith": "^3.0.0", "make-error": "^0.3.0", "ws": "^0.7.1" }, diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js new file mode 100644 index 000000000..3e0ff8517 --- /dev/null +++ b/packages/xo-lib/xo.js @@ -0,0 +1,278 @@ +'use strict'; + +//==================================================================== + +var Bluebird = require('bluebird'); +var isString = require('lodash.isstring'); +var startsWith = require('lodash.startsWith'); + +var Api = require('./api'); +var ConnectionError = require('./connection-error'); +var createCollection = require('./collection'); + +//==================================================================== + +// Expose Bluebird for now to ease integration (e.g. with Angular.js). +exports.setScheduler = Bluebird.setScheduler; + +//==================================================================== + +function makeStandaloneDeferred() { + var resolve, reject; + + var promise = new Bluebird(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + promise.resolve = resolve; + promise.reject = reject; + + return promise; +} + +function noop() {} + +//==================================================================== + +function returnThis() { + /* jshint validthis: true */ + + return this; +} + +// Returns an iterator to the Fibonacci sequence. +function fibonacci(start) { + var prev = 0; + var curr = start || 1; + + var iterator = { + next: function () { + var tmp = curr; + curr += prev; + prev = tmp; + + return { + done: false, + value: prev, + }; + }, + }; + + // Make the iterator a true iterable (ES6). + if (typeof Symbol !== 'undefined') { + iterator[Symbol.iterator] = returnThis; + } + + return iterator; +} + +//-------------------------------------------------------------------- + +function BackOff(generator, opts) { + if (!opts) { + opts = {}; + } + + this._attempts = 0; + this._generator = generator; + this._iterator = generator(); + this._maxAttempts = opts.maxAttempts || Infinity; +} + +BackOff.prototype.wait = function () { + var maxAttempts = this._maxAttempts; + if (this._attempts++ > maxAttempts) { + return Bluebird.reject(new Error( + 'maximum attempts reached (' + maxAttempts +')' + )); + } + + return Bluebird.delay(this._iterator.next().value); +}; + +BackOff.prototype.reset = function () { + this._attempts = 0; + this._iterator = this._generator(); +}; + +//==================================================================== + +var objectsOptions = { + indexes: [ + 'ref', + 'type', + 'UUID', + ], + key: function (item) { + return item.UUID || item.ref; + }, +}; + +// Try connecting to Xo-Server. +function tryConnect() { + /* jshint validthis: true */ + + this.status = 'connecting'; + return this._api.connect().bind(this).catch(function () { + this.status = 'disconnected'; + + return this._backOff.wait().bind(this).then(tryConnect); + }); +} + +function resetSession() { + /* jshint validthis: true */ + + // No session has been opened and no credentials has been provided + // yet: nothing to do. + if (this._credentials && this._credentials.isPending()) { + return; + } + + // Clear any existing user. + this.user = null; + + // Create a promise for the next credentials. + this._credentials = makeStandaloneDeferred(); + + // The promise from the previous session needs to be rejected. + if (this._session && this._session.isPending()) { + // Ensure Bluebird does not mark this rejection as unhandled. + this._session.catch(noop); + + this._session.reject(); + } + + // Create a promise for the next session. + this._session = makeStandaloneDeferred(); +} + +function signIn() { + /* jshint validthis: true */ + + // Capture current session. + var session = this._session; + + this._credentials.bind(this).then(function (credentials) { + return this._api.call( + credentials.token ? + 'session.signInWithToken' : + 'session.signInWithPassword', + credentials + ); + }).then( + function (user) { + this.user = user; + + this._api.call('xo.getAllObjects').bind(this).then(function (objects) { + this.objects.clear(); + this.objects.setMultiple(objects); + }).catch(noop); // Ignore any errors. + + session.resolve(); + }, + function (error) { + session.reject(error); + } + ); +} + +// High level interface to Xo. +// +// Handle auto-reconnect, sign in & objects cache. +function Xo(opts) { + var self = this; + + if (!opts) { + opts = {}; + } else if (isString(opts)) { + opts = { + url: opts, + }; + } + + this._api = new Api(opts.url); + this._backOff = new BackOff(function () { + return fibonacci(1e3); + }); + this.objects = createCollection(objectsOptions); + this.status = 'disconnected'; + + self._api.on('connected', function () { + self.status = 'connected'; + self._backOff.reset(); + + signIn.call(self); + }); + + self._api.on('disconnected', function () { + self.status = 'disconnected'; + + resetSession.call(self); + tryConnect.call(self); + }); + + self._api.on('notification', function (notification) { + if (notification.method !== 'all') { + return; + } + + var method = ( + notification.params.type === 'exit' ? + 'unset' : + 'set' + ) + 'Multiple'; + + self.objects[method](notification.params.items); + }); + + resetSession.call(this); + + if (opts.credentials) { + this._credentials.resolve(opts.credentials); + } + + tryConnect.call(this); +} + +Xo.prototype.call = function (method, params) { + // Prevent session.*() from being because it may interfere + // with this class session management. + if (startsWith(method, 'session.')) { + return Bluebird.reject( + new Error('session.*() methods are disabled from this interface') + ); + } + + return this._session.bind(this).then(function () { + return this._api.call(method, params).bind(this).catch(ConnectionError, function () { + // Retry automatically. + return this.call(method, params); + }); + }); +}; + +Xo.prototype.signIn = function (credentials) { + // Ignore the returned promise as it can cause concurrency issues. + this.signOut(); + + this._credentials.resolve(credentials); + + return this._session; +}; + +Xo.prototype.signOut = function () { + // Already signed in? + var promise; + if (!this._session.isPending()) { + promise = this._api.call('session.signOut'); + } + + resetSession.call(this); + + signIn.call(this); + + return promise || Bluebird.resolve(); +}; + +exports.Xo = Xo; From 7c89d658f710fd6c286267f7341e9694073ddeaf Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 17:37:12 +0100 Subject: [PATCH 069/134] Fix setScheduler(). --- packages/xo-lib/index.js | 3 +++ packages/xo-lib/xo.js | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index 7b55b18c4..eb097768f 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -1,4 +1,7 @@ 'use strict'; +// Expose Bluebird for now to ease integration (e.g. with Angular.js). +exports.setScheduler = require('bluebird').setScheduler; + exports.Api = require('./api'); exports.Xo = require('./xo'); diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 3e0ff8517..907312452 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -12,11 +12,6 @@ var createCollection = require('./collection'); //==================================================================== -// Expose Bluebird for now to ease integration (e.g. with Angular.js). -exports.setScheduler = Bluebird.setScheduler; - -//==================================================================== - function makeStandaloneDeferred() { var resolve, reject; From d76cb440f9a5b23c111430dd482be2e09e58a69c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 17:41:30 +0100 Subject: [PATCH 070/134] Move BackOff to its own module. --- packages/xo-lib/back-off.js | 76 +++++++++++++++++++++++++++++++++++++ packages/xo-lib/xo.js | 62 +----------------------------- 2 files changed, 77 insertions(+), 61 deletions(-) create mode 100644 packages/xo-lib/back-off.js diff --git a/packages/xo-lib/back-off.js b/packages/xo-lib/back-off.js new file mode 100644 index 000000000..3f6834df0 --- /dev/null +++ b/packages/xo-lib/back-off.js @@ -0,0 +1,76 @@ +'use strict'; + +//==================================================================== + +var Bluebird = require('bluebird'); + +//==================================================================== + +function returnThis() { + /* jshint validthis: true */ + + return this; +} + +// Returns an iterator to the Fibonacci sequence. +function fibonacci(start) { + var prev = 0; + var curr = start || 1; + + var iterator = { + next: function () { + var tmp = curr; + curr += prev; + prev = tmp; + + return { + done: false, + value: prev, + }; + }, + }; + + // Make the iterator a true iterable (ES6). + if (typeof Symbol !== 'undefined') { + iterator[Symbol.iterator] = returnThis; + } + + return iterator; +} + +//==================================================================== + +function defaultGenerator() { + return fibonacci(1e3); +} + +function BackOff(opts) { + if (!opts) { + opts = {}; + } + + this._attempts = 0; + this._generator = opts.generator || defaultGenerator; + this._iterator = this._generator(); + this._maxAttempts = opts.maxAttempts || Infinity; +} + +BackOff.prototype.wait = function () { + var maxAttempts = this._maxAttempts; + if (this._attempts++ > maxAttempts) { + return Bluebird.reject(new Error( + 'maximum attempts reached (' + maxAttempts +')' + )); + } + + return Bluebird.delay(this._iterator.next().value); +}; + +BackOff.prototype.reset = function () { + this._attempts = 0; + this._iterator = this._generator(); +}; + +//==================================================================== + +module.exports = BackOff; diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 907312452..00c31e051 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -7,6 +7,7 @@ var isString = require('lodash.isstring'); var startsWith = require('lodash.startsWith'); var Api = require('./api'); +var BackOff = require('./back-off'); var ConnectionError = require('./connection-error'); var createCollection = require('./collection'); @@ -29,67 +30,6 @@ function noop() {} //==================================================================== -function returnThis() { - /* jshint validthis: true */ - - return this; -} - -// Returns an iterator to the Fibonacci sequence. -function fibonacci(start) { - var prev = 0; - var curr = start || 1; - - var iterator = { - next: function () { - var tmp = curr; - curr += prev; - prev = tmp; - - return { - done: false, - value: prev, - }; - }, - }; - - // Make the iterator a true iterable (ES6). - if (typeof Symbol !== 'undefined') { - iterator[Symbol.iterator] = returnThis; - } - - return iterator; -} - -//-------------------------------------------------------------------- - -function BackOff(generator, opts) { - if (!opts) { - opts = {}; - } - - this._attempts = 0; - this._generator = generator; - this._iterator = generator(); - this._maxAttempts = opts.maxAttempts || Infinity; -} - -BackOff.prototype.wait = function () { - var maxAttempts = this._maxAttempts; - if (this._attempts++ > maxAttempts) { - return Bluebird.reject(new Error( - 'maximum attempts reached (' + maxAttempts +')' - )); - } - - return Bluebird.delay(this._iterator.next().value); -}; - -BackOff.prototype.reset = function () { - this._attempts = 0; - this._iterator = this._generator(); -}; - //==================================================================== var objectsOptions = { From 1d3616ae71d5d7d1059e08031579c376247fd322 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 17:41:50 +0100 Subject: [PATCH 071/134] Minor changes. --- packages/xo-lib/xo.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 00c31e051..ec58ca925 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -30,19 +30,6 @@ function noop() {} //==================================================================== -//==================================================================== - -var objectsOptions = { - indexes: [ - 'ref', - 'type', - 'UUID', - ], - key: function (item) { - return item.UUID || item.ref; - }, -}; - // Try connecting to Xo-Server. function tryConnect() { /* jshint validthis: true */ @@ -127,10 +114,17 @@ function Xo(opts) { } this._api = new Api(opts.url); - this._backOff = new BackOff(function () { - return fibonacci(1e3); + this._backOff = new BackOff(); + this.objects = createCollection({ + indexes: [ + 'ref', + 'type', + 'UUID', + ], + key: function (item) { + return item.UUID || item.ref; + }, }); - this.objects = createCollection(objectsOptions); this.status = 'disconnected'; self._api.on('connected', function () { From de76afea99db1bb0952a824d366ae1583fcd535c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 18:22:11 +0100 Subject: [PATCH 072/134] New implementation of Xo (fixes many issues). --- packages/xo-lib/api.js | 4 +- packages/xo-lib/session-error.js | 9 ++ packages/xo-lib/xo.js | 249 +++++++++++++++---------------- 3 files changed, 135 insertions(+), 127 deletions(-) create mode 100644 packages/xo-lib/session-error.js diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index 32dbb20a1..b1b7e0b00 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -7,11 +7,11 @@ var EventEmitter = require('events').EventEmitter; var inherits = require('util').inherits; var jsonRpc = require('json-rpc'); var MethodNotFound = require('json-rpc/errors').MethodNotFound; -var startsWith = require('lodash.startsWith'); +var startsWith = require('lodash.startswith'); var WebSocket = require('ws'); var ConnectionError = require('./connection-error'); -var fixUrl = require('./fixUrl'); +var fixUrl = require('./fix-url'); //==================================================================== diff --git a/packages/xo-lib/session-error.js b/packages/xo-lib/session-error.js new file mode 100644 index 000000000..fe5970f54 --- /dev/null +++ b/packages/xo-lib/session-error.js @@ -0,0 +1,9 @@ +'use strict'; + +//==================================================================== + +var makeError = require('make-error'); + +//==================================================================== + +module.exports = makeError('SessionError'); diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index ec58ca925..ab69cc6ac 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -4,12 +4,13 @@ var Bluebird = require('bluebird'); var isString = require('lodash.isstring'); -var startsWith = require('lodash.startsWith'); +var startsWith = require('lodash.startswith'); var Api = require('./api'); var BackOff = require('./back-off'); var ConnectionError = require('./connection-error'); var createCollection = require('./collection'); +var SessionError = require('./session-error'); //==================================================================== @@ -30,81 +31,7 @@ function noop() {} //==================================================================== -// Try connecting to Xo-Server. -function tryConnect() { - /* jshint validthis: true */ - - this.status = 'connecting'; - return this._api.connect().bind(this).catch(function () { - this.status = 'disconnected'; - - return this._backOff.wait().bind(this).then(tryConnect); - }); -} - -function resetSession() { - /* jshint validthis: true */ - - // No session has been opened and no credentials has been provided - // yet: nothing to do. - if (this._credentials && this._credentials.isPending()) { - return; - } - - // Clear any existing user. - this.user = null; - - // Create a promise for the next credentials. - this._credentials = makeStandaloneDeferred(); - - // The promise from the previous session needs to be rejected. - if (this._session && this._session.isPending()) { - // Ensure Bluebird does not mark this rejection as unhandled. - this._session.catch(noop); - - this._session.reject(); - } - - // Create a promise for the next session. - this._session = makeStandaloneDeferred(); -} - -function signIn() { - /* jshint validthis: true */ - - // Capture current session. - var session = this._session; - - this._credentials.bind(this).then(function (credentials) { - return this._api.call( - credentials.token ? - 'session.signInWithToken' : - 'session.signInWithPassword', - credentials - ); - }).then( - function (user) { - this.user = user; - - this._api.call('xo.getAllObjects').bind(this).then(function (objects) { - this.objects.clear(); - this.objects.setMultiple(objects); - }).catch(noop); // Ignore any errors. - - session.resolve(); - }, - function (error) { - session.reject(error); - } - ); -} - -// High level interface to Xo. -// -// Handle auto-reconnect, sign in & objects cache. function Xo(opts) { - var self = this; - if (!opts) { opts = {}; } else if (isString(opts)) { @@ -113,8 +40,38 @@ function Xo(opts) { }; } - this._api = new Api(opts.url); - this._backOff = new BackOff(); + //------------------------------------------------------------------ + + var api = new Api(opts.url); + + api.on('connected', function () { + this._backOff.reset(); + this.status = 'connected'; + + this._tryToOpenSession(); + }.bind(this)); + + api.on('disconnected', function () { + this._closeSession(); + this._connect(); + }.bind(this)); + + api.on('notification', function (notification) { + if (notification.method !== 'all') { + return; + } + + var method = ( + notification.params.type === 'exit' ? + 'unset' : + 'set' + ) + 'Multiple'; + + this.objects[method](notification.params.items); + }.bind(this)); + + //------------------------------------------------------------------ + this.objects = createCollection({ indexes: [ 'ref', @@ -126,42 +83,17 @@ function Xo(opts) { }, }); this.status = 'disconnected'; + this.user = null; - self._api.on('connected', function () { - self.status = 'connected'; - self._backOff.reset(); + this._api = api; + this._backOff = new BackOff(); + this._credentials = opts.creadentials; + this._session = makeStandaloneDeferred(); + this._signIn = null; - signIn.call(self); - }); + //------------------------------------------------------------------ - self._api.on('disconnected', function () { - self.status = 'disconnected'; - - resetSession.call(self); - tryConnect.call(self); - }); - - self._api.on('notification', function (notification) { - if (notification.method !== 'all') { - return; - } - - var method = ( - notification.params.type === 'exit' ? - 'unset' : - 'set' - ) + 'Multiple'; - - self.objects[method](notification.params.items); - }); - - resetSession.call(this); - - if (opts.credentials) { - this._credentials.resolve(opts.credentials); - } - - tryConnect.call(this); + this._connect(); } Xo.prototype.call = function (method, params) { @@ -174,34 +106,101 @@ Xo.prototype.call = function (method, params) { } return this._session.bind(this).then(function () { - return this._api.call(method, params).bind(this).catch(ConnectionError, function () { - // Retry automatically. - return this.call(method, params); - }); + return this._api.call(method, params); + }).catch(ConnectionError, SessionError, function () { + // Automatically requeue this call. + return this.call(method, params); }); }; Xo.prototype.signIn = function (credentials) { - // Ignore the returned promise as it can cause concurrency issues. this.signOut(); - this._credentials.resolve(credentials); + this._credentials = credentials; + this._signIn = makeStandaloneDeferred(); - return this._session; + this._tryToOpenSession(); + + return this._signIn; }; Xo.prototype.signOut = function () { - // Already signed in? - var promise; - if (!this._session.isPending()) { - promise = this._api.call('session.signOut'); + this._closeSession(); + this._credentials = null; + + var signIn = this._signIn; + if (signIn && signIn.isPending()) { + signIn.reject(new SessionError('sign in aborted')); } - resetSession.call(this); + return this.status === 'connected' ? - signIn.call(this); + // Attempt to sign out and ignore any return values and errors. + this._api.call('session.signOut').then(noop, noop) : - return promise || Bluebird.resolve(); + // Always return a promise. + Bluebird.resolve() + ; }; -exports.Xo = Xo; +Xo.prototype._connect = function _connect() { + this.status = 'connecting'; + + return this._api.connect().bind(this).catch(function (error) { + console.warn('could not connect:', error); + + return this._backOff.wait().bind(this).then(_connect); + }); +}; + +Xo.prototype._closeSession = function () { + if (!this._session.isPending()) { + this._session = makeStandaloneDeferred(); + } + + this.user = null; +}; + +Xo.prototype._tryToOpenSession = function () { + var credentials = this._credentials; + if (!credentials || this.status !== 'connected') { + return; + } + + this._api.call( + credentials.token ? + 'session.signInWithToken' : + 'session.signInWithPassword', + credentials + ).bind(this).then( + function (user) { + this.user = user; + + this._api.call('xo.getAllObjects').bind(this).then(function (objects) { + this.objects.clear(); + this.objects.setMultiple(objects); + }); + + // Validate the sign in. + var signIn = this._signIn; + if (signIn) { + signIn.resolve(); + } + + // Open the session. + this._session.resolve(); + }, + + function (error) { + // Reject the sign in. + var signIn = this._signIn; + if (signIn) { + signIn.reject(error); + } + } + ); +}; + +//==================================================================== + +module.exports = Xo; From c539dd557032f24b2188043e365399ccbf70827c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 4 Mar 2015 18:23:21 +0100 Subject: [PATCH 073/134] 0.6.3 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 3e5d4c54f..41caf92c4 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.6.2", + "version": "0.6.3", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 6fa2e79c1c1907b1d609bc162eb10b53f5f25430 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 17 Apr 2015 13:35:33 +0200 Subject: [PATCH 074/134] Use standard code style. --- packages/xo-lib/.jshintrc | 93 -------------- packages/xo-lib/api.js | 118 +++++++++--------- packages/xo-lib/back-off.js | 74 +++++------ packages/xo-lib/collection.js | 142 ++++++++++----------- packages/xo-lib/connection-error.js | 10 +- packages/xo-lib/example.js | 58 ++++----- packages/xo-lib/fix-url.js | 22 ++-- packages/xo-lib/fix-url.spec.js | 52 ++++---- packages/xo-lib/index.js | 8 +- packages/xo-lib/package.json | 8 +- packages/xo-lib/session-error.js | 10 +- packages/xo-lib/xo.js | 185 ++++++++++++++-------------- 12 files changed, 346 insertions(+), 434 deletions(-) delete mode 100644 packages/xo-lib/.jshintrc diff --git a/packages/xo-lib/.jshintrc b/packages/xo-lib/.jshintrc deleted file mode 100644 index 9e30c8a86..000000000 --- a/packages/xo-lib/.jshintrc +++ /dev/null @@ -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 -} diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index b1b7e0b00..501883920 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -1,132 +1,132 @@ -'use strict'; +'use strict' -//==================================================================== +// =================================================================== -var Bluebird = require('bluebird'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('util').inherits; -var jsonRpc = require('json-rpc'); -var MethodNotFound = require('json-rpc/errors').MethodNotFound; -var startsWith = require('lodash.startswith'); -var WebSocket = require('ws'); +var Bluebird = require('bluebird') +var EventEmitter = require('events').EventEmitter +var inherits = require('util').inherits +var jsonRpc = require('json-rpc') +var MethodNotFound = require('json-rpc/errors').MethodNotFound +var startsWith = require('lodash.startswith') +var WebSocket = require('ws') -var ConnectionError = require('./connection-error'); -var fixUrl = require('./fix-url'); +var ConnectionError = require('./connection-error') +var fixUrl = require('./fix-url') -//==================================================================== +// =================================================================== -function getCurrentUrl() { +function getCurrentUrl () { /* global window: false */ 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() { - var resolve, reject; +function makeDeferred () { + var resolve, reject var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); + resolve = resolve_ + reject = reject_ + }) return { promise: promise, reject: reject, - resolve: resolve, - }; + resolve: resolve + } } -//-------------------------------------------------------------------- +// ------------------------------------------------------------------- // Low level interface to XO. -function Api(url) { +function Api (url) { // Super constructor. - EventEmitter.call(this); + EventEmitter.call(this) // Fix the URL (ensure correct protocol and /api/ path). - this._url = fixUrl(url || getCurrentUrl()); + this._url = fixUrl(url || getCurrentUrl()) // Will contains the WebSocket. - this._socket = null; + this._socket = null // The JSON-RPC server. - var this_ = this; + var this_ = this this._jsonRpc = jsonRpc.createServer(function (message) { if (message.type === 'notification') { - this_.emit('notification', message); + this_.emit('notification', message) } else { // This object does not support requests. - throw new MethodNotFound(message.method); + throw new MethodNotFound(message.method) } }).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 () { if (this._socket) { - this._socket.close(); + this._socket.close() } -}; +} Api.prototype.connect = Bluebird.method(function () { if (this._socket) { - return; + return } - var deferred = makeDeferred(); + var deferred = makeDeferred() - var opts = {}; + var opts = {} if (startsWith(this._url, 'wss')) { // 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. - var this_ = this; + var this_ = this // When the socket opens, send any queued requests. socket.addEventListener('open', function () { // Resolves the promise. - deferred.resolve(); + deferred.resolve() - this_.emit('connected'); - }); + this_.emit('connected') + }) socket.addEventListener('message', function (message) { - this_._jsonRpc.write(message.data); - }); + this_._jsonRpc.write(message.data) + }) 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. if (deferred.promise.isFulfilled()) { - this_.emit('disconnected'); + this_.emit('disconnected') } - }); + }) socket.addEventListener('error', function (error) { // Fails the connect promise if possible. - deferred.reject(error); - }); + deferred.reject(error) + }) - return deferred.promise; -}); + return deferred.promise +}) Api.prototype.call = function (method, params) { - var jsonRpc = this._jsonRpc; + var jsonRpc = this._jsonRpc return this.connect().then(function () { - return jsonRpc.request(method, params); - }); -}; + return jsonRpc.request(method, params) + }) +} -module.exports = Api; +module.exports = Api diff --git a/packages/xo-lib/back-off.js b/packages/xo-lib/back-off.js index 3f6834df0..d79610c31 100644 --- a/packages/xo-lib/back-off.js +++ b/packages/xo-lib/back-off.js @@ -1,76 +1,76 @@ -'use strict'; +'use strict' -//==================================================================== +// =================================================================== -var Bluebird = require('bluebird'); +var Bluebird = require('bluebird') -//==================================================================== +// =================================================================== -function returnThis() { +function returnThis () { /* jshint validthis: true */ - return this; + return this } // Returns an iterator to the Fibonacci sequence. -function fibonacci(start) { - var prev = 0; - var curr = start || 1; +function fibonacci (start) { + var prev = 0 + var curr = start || 1 var iterator = { next: function () { - var tmp = curr; - curr += prev; - prev = tmp; + var tmp = curr + curr += prev + prev = tmp return { done: false, - value: prev, - }; - }, - }; + value: prev + } + } + } // Make the iterator a true iterable (ES6). if (typeof Symbol !== 'undefined') { - iterator[Symbol.iterator] = returnThis; + iterator[Symbol.iterator] = returnThis } - return iterator; + return iterator } -//==================================================================== +// =================================================================== -function defaultGenerator() { - return fibonacci(1e3); +function defaultGenerator () { + return fibonacci(1e3) } -function BackOff(opts) { +function BackOff (opts) { if (!opts) { - opts = {}; + opts = {} } - this._attempts = 0; - this._generator = opts.generator || defaultGenerator; - this._iterator = this._generator(); - this._maxAttempts = opts.maxAttempts || Infinity; + this._attempts = 0 + this._generator = opts.generator || defaultGenerator + this._iterator = this._generator() + this._maxAttempts = opts.maxAttempts || Infinity } BackOff.prototype.wait = function () { - var maxAttempts = this._maxAttempts; + var maxAttempts = this._maxAttempts if (this._attempts++ > maxAttempts) { 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 () { - this._attempts = 0; - this._iterator = this._generator(); -}; + this._attempts = 0 + this._iterator = this._generator() +} -//==================================================================== +// =================================================================== -module.exports = BackOff; +module.exports = BackOff diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js index 5340c5c82..7bf279b4f 100644 --- a/packages/xo-lib/collection.js +++ b/packages/xo-lib/collection.js @@ -1,160 +1,160 @@ -'use strict'; +'use strict' -//==================================================================== +// =================================================================== -var forEach = require('lodash.foreach'); -var indexOf = require('lodash.indexof'); +var forEach = require('lodash.foreach') +var indexOf = require('lodash.indexof') -//==================================================================== +// =================================================================== -function deleteProperties(obj) { +function deleteProperties (obj) { /* jshint forin: false */ - var prop; + var prop for (prop in obj) { - delete obj[prop]; + delete obj[prop] } } -//==================================================================== +// =================================================================== -function defaultKey(item) { - return item.id || item._id || item; +function defaultKey (item) { + return item.id || item._id || item } -//==================================================================== +// =================================================================== -function getAll() { +function getAll () { /* jshint validthis: true */ - return this._data; + return this._data } -function getIndexes() { +function getIndexes () { /* jshint validthis: true */ - return this._indexes; + return this._indexes } -function Collection(opts) { +function Collection (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) { forEach(opts.indexes, function (field) { - this[field] = Object.create(null); - }, this._indexes); + this[field] = Object.create(null) + }, this._indexes) } - this._data = Object.create(null); + this._data = Object.create(null) // Expose public properties. Object.defineProperties(this, { all: { enumerable: true, - get: getAll, + get: getAll }, indexes: { enumerable: true, - get: getIndexes, - }, - }); + get: getIndexes + } + }) } Collection.prototype.clear = function () { - deleteProperties(this._data); - forEach(this._indexes, deleteProperties); -}; + deleteProperties(this._data) + forEach(this._indexes, deleteProperties) +} -function unsetItemFromIndex(index, field) { +function unsetItemFromIndex (index, field) { /* jshint validthis: true */ - var prop = this[field]; + var prop = this[field] 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) { - return; + return } // The index contains only this one item for this prop. if (items.length === 1) { - delete index[prop]; - return; + delete index[prop] + return } // Remove this item. - items.splice(i, 1); + items.splice(i, 1) } // Internal unset method. -function unset(item, key) { +function unset (item, key) { /* 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 */ - var prop = this[field]; + var prop = this[field] if (!prop) { - return; + return } - var items = index[prop]; + var items = index[prop] if (items) { // Update the items list. - items.push(this); + items.push(this) } else { // Create the items list. - index[prop] = [this]; + index[prop] = [this] } } Collection.prototype.set = function (item) { - var key = this._key(item); + var key = this._key(item) if (!key) { // Ignore empty keys. - return; + return } - var previous = this._data[key]; + var previous = this._data[key] if (previous) { - unset.call(this, previous, key); + unset.call(this, previous, key) } - this._data[key] = item; - forEach(this._indexes, setItemToIndex, item); -}; + this._data[key] = item + forEach(this._indexes, setItemToIndex, item) +} Collection.prototype.unset = function (item) { - var key = this._key(item); - item = this._data[key]; + var key = this._key(item) + item = this._data[key] if (!item) { - return; + return } - unset.call(this, item, this._key(item)); -}; + unset.call(this, item, this._key(item)) +} Collection.prototype.setMultiple = function (items) { - forEach(items, this.set, this); -}; -Collection.prototype.unsetMultiple = function (items) { - forEach(items, this.unset, this); -}; - -//==================================================================== - -function createCollection(opts) { - return new Collection(opts); + forEach(items, this.set, this) } -module.exports = createCollection; +Collection.prototype.unsetMultiple = function (items) { + forEach(items, this.unset, this) +} + +// =================================================================== + +function createCollection (opts) { + return new Collection(opts) +} +module.exports = createCollection diff --git a/packages/xo-lib/connection-error.js b/packages/xo-lib/connection-error.js index 79f959f7f..a0f6ab1a0 100644 --- a/packages/xo-lib/connection-error.js +++ b/packages/xo-lib/connection-error.js @@ -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') diff --git a/packages/xo-lib/example.js b/packages/xo-lib/example.js index f6fbedcec..d87727dec 100644 --- a/packages/xo-lib/example.js +++ b/packages/xo-lib/example.js @@ -1,34 +1,36 @@ -var xoLib = require('./'); +'use strict' + +var xoLib = require('./') var xo = new xoLib.Xo({ - url: 'localhost:9000', -}); + url: 'localhost:9000' +}) xo.call('acl.get', {}).then(function (result) { - console.log('baz', result); -}).catch(function (error) { - console.log('error', error) -}); - -xo.signIn({ - email: 'admin@admin.net', - password: 'admin', -}).then(function () { - console.log('foo', xo.user); -}).catch(function (error) { - console.log('error', error) -}); - -xo.signIn({ - email: 'tom', - password: 'tom', -}).then(function () { - console.log('bar', xo.user); -}).catch(function (error) { - console.log('error', error) -}); - -xo.call('acl.get', {}).then(function (result) { - console.log('plop', result); + console.log('baz', result) +}).catch(function (error) { + console.log('error', error) +}) + +xo.signIn({ + email: 'admin@admin.net', + password: 'admin' +}).then(function () { + console.log('foo', xo.user) +}).catch(function (error) { + console.log('error', error) +}) + +xo.signIn({ + email: 'tom', + password: 'tom' +}).then(function () { + console.log('bar', xo.user) +}).catch(function (error) { + console.log('error', error) +}) + +xo.call('acl.get', {}).then(function (result) { + console.log('plop', result) }).catch(function (error) { console.log('error', error) }) diff --git a/packages/xo-lib/fix-url.js b/packages/xo-lib/fix-url.js index 6ed8c5bae..d767d5589 100644 --- a/packages/xo-lib/fix-url.js +++ b/packages/xo-lib/fix-url.js @@ -1,21 +1,21 @@ -'use strict'; +'use strict' -//==================================================================== +// =================================================================== // Fix URL if necessary. -var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/; -function fixUrl(url) { - var matches = URL_RE.exec(url); - var isSecure = !!matches[1]; - var hostAndPath = matches[2]; - var search = matches[3]; +var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/ +function fixUrl (url) { + var matches = URL_RE.exec(url) + var isSecure = !!matches[1] + var hostAndPath = matches[2] + var search = matches[3] return [ isSecure ? 'wss' : 'ws', '://', hostAndPath, '/api/', - search, - ].join(''); + search + ].join('') } -module.exports = fixUrl; +module.exports = fixUrl diff --git a/packages/xo-lib/fix-url.spec.js b/packages/xo-lib/fix-url.spec.js index 7ab89d920..2ff751050 100644 --- a/packages/xo-lib/fix-url.spec.js +++ b/packages/xo-lib/fix-url.spec.js @@ -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 () { - var fixUrl = require('./fix-url'); + var fixUrl = require('./fix-url') describe('protocol', 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 () { - expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/'); - expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/'); - }); + expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/') + expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/') + }) it('is not added if already present', function () { - expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/'); - expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/'); - }); - }); + expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/') + expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/') + }) + }) describe('/api/ path', 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 () { - 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 () { - 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 () { - expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo'); - }); - }); -}); + expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo') + }) + }) +}) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index eb097768f..695713bfd 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' // 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.Xo = require('./xo'); +exports.Api = require('./api') +exports.Xo = require('./xo') diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 41caf92c4..5741386cf 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -19,10 +19,12 @@ "node": ">=0.8.0" }, "scripts": { - "test": "mocha *.spec.js" + "test": "standard && mocha '*.spec.js'" }, "dependencies": { "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", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", @@ -30,10 +32,12 @@ "lodash.isstring": "^3.0.0", "lodash.startswith": "^3.0.0", "make-error": "^0.3.0", + "pw": "0.0.4", "ws": "^0.7.1" }, "devDependencies": { "mocha": "^2.1.0", - "must": "^0.12.0" + "must": "^0.12.0", + "standard": "*" } } diff --git a/packages/xo-lib/session-error.js b/packages/xo-lib/session-error.js index fe5970f54..19c54f400 100644 --- a/packages/xo-lib/session-error.js +++ b/packages/xo-lib/session-error.js @@ -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') diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index ab69cc6ac..7d149a2c2 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -1,99 +1,99 @@ -'use strict'; +'use strict' -//==================================================================== +// =================================================================== -var Bluebird = require('bluebird'); -var isString = require('lodash.isstring'); -var startsWith = require('lodash.startswith'); +var Bluebird = require('bluebird') +var isString = require('lodash.isstring') +var startsWith = require('lodash.startswith') -var Api = require('./api'); -var BackOff = require('./back-off'); -var ConnectionError = require('./connection-error'); -var createCollection = require('./collection'); -var SessionError = require('./session-error'); +var Api = require('./api') +var BackOff = require('./back-off') +var ConnectionError = require('./connection-error') +var createCollection = require('./collection') +var SessionError = require('./session-error') -//==================================================================== +// =================================================================== -function makeStandaloneDeferred() { - var resolve, reject; +function makeStandaloneDeferred () { + var resolve, reject var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); - promise.resolve = resolve; - promise.reject = reject; + resolve = resolve_ + reject = reject_ + }) + promise.resolve = resolve + promise.reject = reject - return promise; + return promise } -function noop() {} +function noop () {} -//==================================================================== +// =================================================================== -function Xo(opts) { +function Xo (opts) { if (!opts) { - opts = {}; + opts = {} } else if (isString(opts)) { opts = { - url: opts, - }; + url: opts + } } - //------------------------------------------------------------------ + // ----------------------------------------------------------------- - var api = new Api(opts.url); + var api = new Api(opts.url) api.on('connected', function () { - this._backOff.reset(); - this.status = 'connected'; + this._backOff.reset() + this.status = 'connected' - this._tryToOpenSession(); - }.bind(this)); + this._tryToOpenSession() + }.bind(this)) api.on('disconnected', function () { - this._closeSession(); - this._connect(); - }.bind(this)); + this._closeSession() + this._connect() + }.bind(this)) api.on('notification', function (notification) { if (notification.method !== 'all') { - return; + return } var method = ( notification.params.type === 'exit' ? 'unset' : 'set' - ) + 'Multiple'; + ) + 'Multiple' - this.objects[method](notification.params.items); - }.bind(this)); + this.objects[method](notification.params.items) + }.bind(this)) - //------------------------------------------------------------------ + // ----------------------------------------------------------------- this.objects = createCollection({ indexes: [ 'ref', 'type', - 'UUID', + 'UUID' ], key: function (item) { - return item.UUID || item.ref; - }, - }); - this.status = 'disconnected'; - this.user = null; + return item.UUID || item.ref + } + }) + this.status = 'disconnected' + this.user = null - this._api = api; - this._backOff = new BackOff(); - this._credentials = opts.creadentials; - this._session = makeStandaloneDeferred(); - this._signIn = null; + this._api = api + this._backOff = new BackOff() + this._credentials = opts.creadentials + this._session = makeStandaloneDeferred() + this._signIn = null - //------------------------------------------------------------------ + // ----------------------------------------------------------------- - this._connect(); + this._connect() } Xo.prototype.call = function (method, params) { @@ -102,35 +102,35 @@ Xo.prototype.call = function (method, params) { if (startsWith(method, 'session.')) { return Bluebird.reject( new Error('session.*() methods are disabled from this interface') - ); + ) } return this._session.bind(this).then(function () { - return this._api.call(method, params); + return this._api.call(method, params) }).catch(ConnectionError, SessionError, function () { // Automatically requeue this call. - return this.call(method, params); - }); -}; + return this.call(method, params) + }) +} Xo.prototype.signIn = function (credentials) { - this.signOut(); + this.signOut() - this._credentials = credentials; - this._signIn = makeStandaloneDeferred(); + this._credentials = credentials + this._signIn = makeStandaloneDeferred() - this._tryToOpenSession(); + this._tryToOpenSession() - return this._signIn; -}; + return this._signIn +} Xo.prototype.signOut = function () { - this._closeSession(); - this._credentials = null; + this._closeSession() + this._credentials = null - var signIn = this._signIn; + var signIn = this._signIn if (signIn && signIn.isPending()) { - signIn.reject(new SessionError('sign in aborted')); + signIn.reject(new SessionError('sign in aborted')) } return this.status === 'connected' ? @@ -140,31 +140,30 @@ Xo.prototype.signOut = function () { // Always return a promise. Bluebird.resolve() - ; -}; +} -Xo.prototype._connect = function _connect() { - this.status = 'connecting'; +Xo.prototype._connect = function _connect () { + this.status = 'connecting' 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 () { if (!this._session.isPending()) { - this._session = makeStandaloneDeferred(); + this._session = makeStandaloneDeferred() } - this.user = null; -}; + this.user = null +} Xo.prototype._tryToOpenSession = function () { - var credentials = this._credentials; + var credentials = this._credentials if (!credentials || this.status !== 'connected') { - return; + return } this._api.call( @@ -174,33 +173,33 @@ Xo.prototype._tryToOpenSession = function () { credentials ).bind(this).then( function (user) { - this.user = user; + this.user = user this._api.call('xo.getAllObjects').bind(this).then(function (objects) { - this.objects.clear(); - this.objects.setMultiple(objects); - }); + this.objects.clear() + this.objects.setMultiple(objects) + }) // Validate the sign in. - var signIn = this._signIn; + var signIn = this._signIn if (signIn) { - signIn.resolve(); + signIn.resolve() } // Open the session. - this._session.resolve(); + this._session.resolve() }, function (error) { // Reject the sign in. - var signIn = this._signIn; + var signIn = this._signIn if (signIn) { - signIn.reject(error); + signIn.reject(error) } } - ); -}; + ) +} -//==================================================================== +// =================================================================== -module.exports = Xo; +module.exports = Xo From c19916ff1c7fe195b099753ddd92e1bca4c664b0 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 17 Apr 2015 14:05:49 +0200 Subject: [PATCH 075/134] Add CLI. --- packages/xo-lib/cli.js | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 packages/xo-lib/cli.js diff --git a/packages/xo-lib/cli.js b/packages/xo-lib/cli.js new file mode 100755 index 000000000..26f03d5b6 --- /dev/null +++ b/packages/xo-lib/cli.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +'use strict' + +// =================================================================== + +var Bluebird = require('bluebird') +var createRepl = require('repl').start +var eventToPromise = require('event-to-promise') +var pw = require('pw') + +var Xo = require('./').Xo + +// =================================================================== + +var usage = '' + +function main (args) { + if (args[0] === '--help' || args[0] === 'h') { + return usage + } + + var xo = new Xo(args[0]) + + return new Bluebird(function (resolve) { + process.stdout.write('Password: ') + pw(resolve) + }).then(function (password) { + return xo.signIn({ + email: args[1], + password: password + }) + }).then(function () { + var repl = createRepl({}) + repl.context.xo = xo + + return eventToPromise(repl, 'exit') + }) +} +module.exports = main + +// =================================================================== + +if (!module.parent) { + require('exec-promise')(main) +} From c1db993b929856c4b35a904a9de000cad6c7a0be Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 17 Apr 2015 16:25:18 +0200 Subject: [PATCH 076/134] Fix missing quotes. --- packages/xo-lib/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index 501883920..d333e6aed 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -18,7 +18,7 @@ var fixUrl = require('./fix-url') function getCurrentUrl () { /* global window: false */ - if (typeof window === undefined) { + if (typeof window === 'undefined') { throw new Error('cannot get current URL') } From 99694161e102b834b98a3b730acf948063ee5813 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 21 Apr 2015 17:17:00 +0200 Subject: [PATCH 077/134] The REPL waits for promise completion. --- packages/xo-lib/cli.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/xo-lib/cli.js b/packages/xo-lib/cli.js index 26f03d5b6..8508e2ce2 100755 --- a/packages/xo-lib/cli.js +++ b/packages/xo-lib/cli.js @@ -34,6 +34,15 @@ function main (args) { var repl = createRepl({}) repl.context.xo = xo + // Make the REPL waits for promise completion. + var evaluate = Bluebird.promisify(repl.eval) + repl.eval = function (cmd, context, filename, cb) { + evaluate(cmd, context, filename) + // See https://github.com/petkaantonov/bluebird/issues/594 + .then(function (result) { return result}) + .nodeify(cb) + } + return eventToPromise(repl, 'exit') }) } From c763794ef3f619ac57dfb76498a305c262dc4f96 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 24 Apr 2015 11:21:15 +0200 Subject: [PATCH 078/134] Install json-rpc from the npm repository. --- packages/xo-lib/api.js | 4 ++-- packages/xo-lib/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index d333e6aed..8a56b2dc7 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -5,8 +5,8 @@ var Bluebird = require('bluebird') var EventEmitter = require('events').EventEmitter var inherits = require('util').inherits -var jsonRpc = require('json-rpc') -var MethodNotFound = require('json-rpc/errors').MethodNotFound +var jsonRpc = require('@julien-f/json-rpc') +var MethodNotFound = require('@julien-f/json-rpc/errors').MethodNotFound var startsWith = require('lodash.startswith') var WebSocket = require('ws') diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 5741386cf..9e6dba7c7 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -22,10 +22,10 @@ "test": "standard && mocha '*.spec.js'" }, "dependencies": { + "@julien-f/json-rpc": "^0.3.5", "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", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", "lodash.indexof": "^3.0.0", From 9d05653f5be0958a61e6eeda2c3d8b7d1b1e1e2b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 14 May 2015 17:19:28 +0200 Subject: [PATCH 079/134] Use xo-collection. --- packages/xo-lib/collection.js | 160 ---------------------------------- packages/xo-lib/package.json | 4 +- packages/xo-lib/xo.js | 46 ++++++---- 3 files changed, 30 insertions(+), 180 deletions(-) delete mode 100644 packages/xo-lib/collection.js diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js deleted file mode 100644 index 7bf279b4f..000000000 --- a/packages/xo-lib/collection.js +++ /dev/null @@ -1,160 +0,0 @@ -'use strict' - -// =================================================================== - -var forEach = require('lodash.foreach') -var indexOf = require('lodash.indexof') - -// =================================================================== - -function deleteProperties (obj) { - /* jshint forin: false */ - var prop - for (prop in obj) { - delete obj[prop] - } -} - -// =================================================================== - -function defaultKey (item) { - return item.id || item._id || item -} - -// =================================================================== - -function getAll () { - /* jshint validthis: true */ - return this._data -} - -function getIndexes () { - /* jshint validthis: true */ - return this._indexes -} - -function Collection (opts) { - if (!opts) { - opts = {} - } - - this._key = opts.key || defaultKey - - this._indexes = Object.create(null) - if (opts.indexes) { - forEach(opts.indexes, function (field) { - this[field] = Object.create(null) - }, this._indexes) - } - - this._data = Object.create(null) - - // Expose public properties. - Object.defineProperties(this, { - all: { - enumerable: true, - get: getAll - }, - indexes: { - enumerable: true, - get: getIndexes - } - }) -} - -Collection.prototype.clear = function () { - deleteProperties(this._data) - forEach(this._indexes, deleteProperties) -} - -function unsetItemFromIndex (index, field) { - /* jshint validthis: true */ - - var prop = this[field] - if (!prop) { - return - } - - var items = index[prop] - - var i = indexOf(items, this) - if (i === -1) { - return - } - - // The index contains only this one item for this prop. - if (items.length === 1) { - delete index[prop] - return - } - - // Remove this item. - items.splice(i, 1) -} - -// Internal unset method. -function unset (item, key) { - /* jshint validthis: true */ - - delete this._data[key] - - forEach(this._indexes, unsetItemFromIndex, item) -} - -function setItemToIndex (index, field) { - /* jshint validthis: true */ - - var prop = this[field] - if (!prop) { - return - } - - var items = index[prop] - if (items) { - // Update the items list. - items.push(this) - } else { - // Create the items list. - index[prop] = [this] - } -} - -Collection.prototype.set = function (item) { - var key = this._key(item) - if (!key) { - // Ignore empty keys. - return - } - - var previous = this._data[key] - if (previous) { - unset.call(this, previous, key) - } - - this._data[key] = item - forEach(this._indexes, setItemToIndex, item) -} - -Collection.prototype.unset = function (item) { - var key = this._key(item) - item = this._data[key] - if (!item) { - return - } - - unset.call(this, item, this._key(item)) -} - -Collection.prototype.setMultiple = function (items) { - 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 diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 9e6dba7c7..0d7d1bb73 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -28,12 +28,12 @@ "exec-promise": "^0.5.1", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", - "lodash.indexof": "^3.0.0", "lodash.isstring": "^3.0.0", "lodash.startswith": "^3.0.0", "make-error": "^0.3.0", "pw": "0.0.4", - "ws": "^0.7.1" + "ws": "^0.7.1", + "xo-collection": "^0.2.1" }, "devDependencies": { "mocha": "^2.1.0", diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 7d149a2c2..8e5cc1556 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -3,13 +3,15 @@ // =================================================================== var Bluebird = require('bluebird') +var Collection = require('xo-collection').default +var forEach = require('lodash.foreach') +var Index = require('xo-collection/index') var isString = require('lodash.isstring') var startsWith = require('lodash.startswith') var Api = require('./api') var BackOff = require('./back-off') var ConnectionError = require('./connection-error') -var createCollection = require('./collection') var SessionError = require('./session-error') // =================================================================== @@ -29,6 +31,18 @@ function makeStandaloneDeferred () { function noop () {} +function setMultiple (collection, items) { + forEach(items, function (item) { + collection.set(item) + }) +} + +function unsetMultiple (collection, items) { + forEach(items, function (item) { + collection.unset(item) + }) +} + // =================================================================== function Xo (opts) { @@ -61,27 +75,23 @@ function Xo (opts) { return } - var method = ( - notification.params.type === 'exit' ? - 'unset' : - 'set' - ) + 'Multiple' + var method = notification.params.type === 'exit' ? + unsetMultiple : + setMultiple - this.objects[method](notification.params.items) + method(this.objects, notification.params.items) }.bind(this)) // ----------------------------------------------------------------- - this.objects = createCollection({ - indexes: [ - 'ref', - 'type', - 'UUID' - ], - key: function (item) { - return item.UUID || item.ref - } - }) + var objects = this.objects = new Collection() + objects.getKey = function (item) { + return item.UUID || item.ref || 'undefined' + } + objects.createIndex('ref', new Index('ref')) + objects.createIndex('type', new Index('type')) + objects.createIndex('UUID', new Index('UUID')) + this.status = 'disconnected' this.user = null @@ -177,7 +187,7 @@ Xo.prototype._tryToOpenSession = function () { this._api.call('xo.getAllObjects').bind(this).then(function (objects) { this.objects.clear() - this.objects.setMultiple(objects) + setMultiple(this.objects, objects) }) // Validate the sign in. From 312fcea5f18871536773ed040e1b4739ca27d398 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 15 May 2015 18:08:21 +0200 Subject: [PATCH 080/134] Auto links. --- packages/xo-lib/xo.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 8e5cc1556..fc8d6b4f3 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -31,8 +31,31 @@ function makeStandaloneDeferred () { function noop () {} +// ------------------------------------------------------------------- + +var defineProperty = Object.defineProperty + +var LINK_RE = /^(.*)\$link\$$/ + +function createAutoLinks (collection, object) { + forEach(object, function resolveObject (value, key, object) { + var matches = key.match(LINK_RE) + if (!matches) { + return + } + + defineProperty(object, matches[1], { + get: function () { + return collection[value] + } + }) + }) +} + function setMultiple (collection, items) { forEach(items, function (item) { + createAutoLinks(item) + collection.set(item) }) } @@ -90,7 +113,7 @@ function Xo (opts) { } objects.createIndex('ref', new Index('ref')) objects.createIndex('type', new Index('type')) - objects.createIndex('UUID', new Index('UUID')) + objects.createIndex('uuid', new Index('uuid')) this.status = 'disconnected' this.user = null From 7f60725c88aec65b2a11156d064bdbf782b6cc2b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 19 May 2015 16:48:03 +0200 Subject: [PATCH 081/134] Make it work with current version of xo-web. --- packages/xo-lib/package.json | 2 +- packages/xo-lib/xo.js | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 0d7d1bb73..45f3914e5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -33,7 +33,7 @@ "make-error": "^0.3.0", "pw": "0.0.4", "ws": "^0.7.1", - "xo-collection": "^0.2.1" + "xo-collection": "^0.3.0" }, "devDependencies": { "mocha": "^2.1.0", diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index fc8d6b4f3..2ee6cf5fd 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -8,6 +8,7 @@ var forEach = require('lodash.foreach') var Index = require('xo-collection/index') var isString = require('lodash.isstring') var startsWith = require('lodash.startswith') +var UniqueIndex = require('xo-collection/unique-index') var Api = require('./api') var BackOff = require('./back-off') @@ -62,7 +63,9 @@ function setMultiple (collection, items) { function unsetMultiple (collection, items) { forEach(items, function (item) { - collection.unset(item) + if (collection.has(item)) { + collection.remove(item) + } }) } @@ -111,9 +114,15 @@ function Xo (opts) { objects.getKey = function (item) { return item.UUID || item.ref || 'undefined' } - objects.createIndex('ref', new Index('ref')) + objects.createIndex('ref', new UniqueIndex('ref')) objects.createIndex('type', new Index('type')) - objects.createIndex('uuid', new Index('uuid')) + objects.createIndex('UUID', new UniqueIndex('UUID')) + + // This hack is used to trigger an Angular refresh in xo-web + // every time the collection is updated. + objects.on('finish', function () { + Bluebird.resolve().then(function () {}) + }) this.status = 'disconnected' this.user = null From 13913334b68116f33203bac0a67a91bc3eb275e3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 19 May 2015 16:48:19 +0200 Subject: [PATCH 082/134] 0.7.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 45f3914e5..4fca80ddd 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.6.3", + "version": "0.7.0", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 21f8e4d55bfe876aa8cfd2f3c541899cc90b0e2a Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 May 2015 14:52:19 +0200 Subject: [PATCH 083/134] Remove unused UUID index. --- packages/xo-lib/xo.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 2ee6cf5fd..6612682f2 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -116,8 +116,6 @@ function Xo (opts) { } objects.createIndex('ref', new UniqueIndex('ref')) objects.createIndex('type', new Index('type')) - objects.createIndex('UUID', new UniqueIndex('UUID')) - // This hack is used to trigger an Angular refresh in xo-web // every time the collection is updated. objects.on('finish', function () { From 3589dda8ee555e59d042bad32c63ec19c412bdc8 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 May 2015 14:52:27 +0200 Subject: [PATCH 084/134] Remove ugly hack. --- packages/xo-lib/xo.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 6612682f2..5994a9541 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -116,11 +116,6 @@ function Xo (opts) { } objects.createIndex('ref', new UniqueIndex('ref')) objects.createIndex('type', new Index('type')) - // This hack is used to trigger an Angular refresh in xo-web - // every time the collection is updated. - objects.on('finish', function () { - Bluebird.resolve().then(function () {}) - }) this.status = 'disconnected' this.user = null From a17f718517ee3c8ceeacf076065d4d3504da1fb4 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 May 2015 14:53:46 +0200 Subject: [PATCH 085/134] Update xo-collection to 0.3.1. --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 4fca80ddd..2a157896d 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -33,7 +33,7 @@ "make-error": "^0.3.0", "pw": "0.0.4", "ws": "^0.7.1", - "xo-collection": "^0.3.0" + "xo-collection": "^0.3.1" }, "devDependencies": { "mocha": "^2.1.0", From d8ca15ceb3b96720660dbb62ec23cc0e24cf36ea Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 May 2015 15:12:22 +0200 Subject: [PATCH 086/134] Fix objects removal. --- packages/xo-lib/xo.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 5994a9541..4496aa5a0 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -54,17 +54,31 @@ function createAutoLinks (collection, object) { } function setMultiple (collection, items) { + const getKey = collection.getKey + forEach(items, function (item) { + const key = getKey(item) + if (!key) { + return + } + createAutoLinks(item) - collection.set(item) + collection.set(key, item) }) } function unsetMultiple (collection, items) { + const getKey = collection.getKey + forEach(items, function (item) { - if (collection.has(item)) { - collection.remove(item) + const key = getKey(item) + if (!key) { + return + } + + if (collection.has(key)) { + collection.remove(key) } }) } From 5a87a6c502f355bd3c418c631c143226ce4bca86 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 May 2015 16:41:33 +0200 Subject: [PATCH 087/134] This lib is not ES6! --- packages/xo-lib/xo.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 4496aa5a0..77cd5facc 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -54,10 +54,10 @@ function createAutoLinks (collection, object) { } function setMultiple (collection, items) { - const getKey = collection.getKey + var getKey = collection.getKey forEach(items, function (item) { - const key = getKey(item) + var key = getKey(item) if (!key) { return } @@ -69,10 +69,10 @@ function setMultiple (collection, items) { } function unsetMultiple (collection, items) { - const getKey = collection.getKey + var getKey = collection.getKey forEach(items, function (item) { - const key = getKey(item) + var key = getKey(item) if (!key) { return } From 68abd91fc2636d3e47ce759a3c3d692f72fc8086 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sun, 24 May 2015 14:41:01 +0200 Subject: [PATCH 088/134] Api#close() returns a promise. --- packages/xo-lib/api.js | 9 +++++++-- packages/xo-lib/package.json | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index 8a56b2dc7..aff1320b2 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -4,6 +4,7 @@ var Bluebird = require('bluebird') var EventEmitter = require('events').EventEmitter +var eventToPromise = require('event-to-promise') var inherits = require('util').inherits var jsonRpc = require('@julien-f/json-rpc') var MethodNotFound = require('@julien-f/json-rpc/errors').MethodNotFound @@ -68,9 +69,13 @@ function Api (url) { inherits(Api, EventEmitter) Api.prototype.close = function () { - if (this._socket) { - this._socket.close() + var socket = this._socket + if (socket) { + socket.close() + return eventToPromise(socket, 'close') } + + return Bluebird.resolve() } Api.prototype.connect = Bluebird.method(function () { diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 2a157896d..368f92cd0 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -24,7 +24,7 @@ "dependencies": { "@julien-f/json-rpc": "^0.3.5", "bluebird": "^2.9.6", - "event-to-promise": "^0.3.2", + "event-to-promise": "^0.3.3", "exec-promise": "^0.5.1", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", From 41280c9d38cf534fe8c6bbc05d1e61b111b56a33 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 May 2015 12:18:41 +0200 Subject: [PATCH 089/134] Various updates. --- packages/xo-lib/xo.js | 78 +++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 77cd5facc..bfc6acf92 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -17,6 +17,16 @@ var SessionError = require('./session-error') // =================================================================== +function bind (fn, thisArg) { + if (!fn) { + return fn + } + + return function () { + return fn.apply(thisArg, arguments) + } +} + function makeStandaloneDeferred () { var resolve, reject @@ -32,51 +42,56 @@ function makeStandaloneDeferred () { function noop () {} +var trace = + bind(console.trace, console) || + bind(console.log, console) || + noop + // ------------------------------------------------------------------- var defineProperty = Object.defineProperty -var LINK_RE = /^(.*)\$link\$$/ +function getDeprecatedUUID () { + // trace('.UUID is deprecated, use .id instead') -function createAutoLinks (collection, object) { - forEach(object, function resolveObject (value, key, object) { - var matches = key.match(LINK_RE) - if (!matches) { - return - } + return this.id +} - defineProperty(object, matches[1], { - get: function () { - return collection[value] - } - }) +function defineDeprecatedUUID (object) { + defineProperty(object, 'UUID', { + get: getDeprecatedUUID }) } +// var LINK_RE = /^(.*)\$link\$$/ +// function createAutoLinks (collection, object) { +// var all = collection.all + +// forEach(object, function resolveObject (value, key, object) { +// var matches = key.match(LINK_RE) +// if (!matches) { +// return +// } + +// defineProperty(object, matches[1], { +// get: function () { +// return all[value] +// } +// }) +// }) +// } + function setMultiple (collection, items) { - var getKey = collection.getKey - - forEach(items, function (item) { - var key = getKey(item) - if (!key) { - return - } - - createAutoLinks(item) + forEach(items, function (item, key) { + defineDeprecatedUUID(item) + // createAutoLinks(collection, item) collection.set(key, item) }) } function unsetMultiple (collection, items) { - var getKey = collection.getKey - - forEach(items, function (item) { - var key = getKey(item) - if (!key) { - return - } - + forEach(items, function (_, key) { if (collection.has(key)) { collection.remove(key) } @@ -119,15 +134,14 @@ function Xo (opts) { unsetMultiple : setMultiple + console.log(notification.params) + method(this.objects, notification.params.items) }.bind(this)) // ----------------------------------------------------------------- var objects = this.objects = new Collection() - objects.getKey = function (item) { - return item.UUID || item.ref || 'undefined' - } objects.createIndex('ref', new UniqueIndex('ref')) objects.createIndex('type', new Index('type')) From 44d4096a79ebd2920b0fbb9a86824371a2431409 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 May 2015 14:07:14 +0200 Subject: [PATCH 090/134] Remove a console.log(). --- packages/xo-lib/xo.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index bfc6acf92..50753ecc8 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -134,8 +134,6 @@ function Xo (opts) { unsetMultiple : setMultiple - console.log(notification.params) - method(this.objects, notification.params.items) }.bind(this)) From 1839bf938a76e87192301b8c191a9fd436be1a17 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 May 2015 17:02:25 +0200 Subject: [PATCH 091/134] 0.7.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 368f92cd0..24c282812 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.7.0", + "version": "0.7.1", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 7a4cdf86884b4952ddfc193986cb0794b6caf153 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:33:49 +0200 Subject: [PATCH 092/134] Fix connection errors handling in Api#connect(). --- packages/xo-lib/api.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index aff1320b2..783fd5d09 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -40,6 +40,8 @@ function makeDeferred () { } } +function noop () {} + // ------------------------------------------------------------------- // Low level interface to XO. @@ -50,6 +52,9 @@ function Api (url) { // Fix the URL (ensure correct protocol and /api/ path). this._url = fixUrl(url || getCurrentUrl()) + // Will contains the connection promise. + this._connection = null + // Will contains the WebSocket. this._socket = null @@ -72,18 +77,23 @@ Api.prototype.close = function () { var socket = this._socket if (socket) { socket.close() - return eventToPromise(socket, 'close') + + console.log(socket.readyState) + if (socket.readyState !== 3) { + return eventToPromise(socket, 'close').then(noop) + } } return Bluebird.resolve() } -Api.prototype.connect = Bluebird.method(function () { - if (this._socket) { - return +Api.prototype.connect = function () { + if (this._connection) { + return this._connection } var deferred = makeDeferred() + this._connection = deferred.promise var opts = {} if (startsWith(this._url, 'wss')) { @@ -103,11 +113,20 @@ Api.prototype.connect = Bluebird.method(function () { this_.emit('connected') }) + socket.addEventListener('error', function (error) { + this_._connection = null + this_._socket = null + + // Fails the connect promise if possible. + deferred.reject(error) + }) + socket.addEventListener('message', function (message) { this_._jsonRpc.write(message.data) }) socket.addEventListener('close', function () { + this_._connection = null this_._socket = null this_._jsonRpc.failPendingRequests(new ConnectionError()) @@ -118,13 +137,8 @@ Api.prototype.connect = Bluebird.method(function () { } }) - socket.addEventListener('error', function (error) { - // Fails the connect promise if possible. - deferred.reject(error) - }) - return deferred.promise -}) +} Api.prototype.call = function (method, params) { var jsonRpc = this._jsonRpc From e3879cd4d1f9c65452c6532e92ce2c72ba8a19f4 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:34:41 +0200 Subject: [PATCH 093/134] Minor style fix. --- packages/xo-lib/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/cli.js b/packages/xo-lib/cli.js index 8508e2ce2..bf6a5d272 100755 --- a/packages/xo-lib/cli.js +++ b/packages/xo-lib/cli.js @@ -39,7 +39,7 @@ function main (args) { repl.eval = function (cmd, context, filename, cb) { evaluate(cmd, context, filename) // See https://github.com/petkaantonov/bluebird/issues/594 - .then(function (result) { return result}) + .then(function (result) { return result }) .nodeify(cb) } From 0f30cc8e5926f9b77e5e457b810a481e0e3df5e5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:35:27 +0200 Subject: [PATCH 094/134] Work around linter issue (eslint/espree#136). --- packages/xo-lib/cli.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/xo-lib/cli.js b/packages/xo-lib/cli.js index bf6a5d272..9c1af26fd 100755 --- a/packages/xo-lib/cli.js +++ b/packages/xo-lib/cli.js @@ -2,8 +2,6 @@ 'use strict' -// =================================================================== - var Bluebird = require('bluebird') var createRepl = require('repl').start var eventToPromise = require('event-to-promise') From 2653ff6536b365017a4b0ff40e76829db373013b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:36:04 +0200 Subject: [PATCH 095/134] Comment currently unused declarations. --- packages/xo-lib/xo.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 50753ecc8..35df5859f 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -17,15 +17,15 @@ var SessionError = require('./session-error') // =================================================================== -function bind (fn, thisArg) { - if (!fn) { - return fn - } +// function bind (fn, thisArg) { +// if (!fn) { +// return fn +// } - return function () { - return fn.apply(thisArg, arguments) - } -} +// return function () { +// return fn.apply(thisArg, arguments) +// } +// } function makeStandaloneDeferred () { var resolve, reject @@ -42,10 +42,10 @@ function makeStandaloneDeferred () { function noop () {} -var trace = - bind(console.trace, console) || - bind(console.log, console) || - noop +// var trace = +// bind(console.trace, console) || +// bind(console.log, console) || +// noop // ------------------------------------------------------------------- From 0b01a79d9dc4340aacc3ec28af9663ef58aa1827 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:36:27 +0200 Subject: [PATCH 096/134] Implements object.messages. --- packages/xo-lib/xo.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 35df5859f..c684d6838 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -82,10 +82,18 @@ function defineDeprecatedUUID (object) { // } function setMultiple (collection, items) { + var messages = collection.indexes.messagesByObject + forEach(items, function (item, key) { defineDeprecatedUUID(item) // createAutoLinks(collection, item) + defineProperty(item, 'messages', { + get: function () { + return messages[key] + } + }) + collection.set(key, item) }) } @@ -142,6 +150,11 @@ function Xo (opts) { var objects = this.objects = new Collection() objects.createIndex('ref', new UniqueIndex('ref')) objects.createIndex('type', new Index('type')) + objects.createIndex('messagesByObject', new Index(function (obj) { + if (obj.type === 'message') { + return obj.$object + } + })) this.status = 'disconnected' this.user = null From 725f471a6a565fe0c47e1cacfa88356efae53d18 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 28 May 2015 15:36:49 +0200 Subject: [PATCH 097/134] 0.7.2 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 24c282812..b5e68b3df 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.7.1", + "version": "0.7.2", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 2f2ee1f431746e4c8733d72ab0ef642a988637a5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 24 Sep 2015 16:39:54 +0200 Subject: [PATCH 098/134] Update ws 0.8.0 to support Node 4. --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index b5e68b3df..5da145629 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -32,7 +32,7 @@ "lodash.startswith": "^3.0.0", "make-error": "^0.3.0", "pw": "0.0.4", - "ws": "^0.7.1", + "ws": "^0.8.0", "xo-collection": "^0.3.1" }, "devDependencies": { From 4fd9639457ace264883a18d4b781c9bcc59d23b9 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 24 Sep 2015 16:40:31 +0200 Subject: [PATCH 099/134] 0.7.3 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 5da145629..3f371422e 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.7.2", + "version": "0.7.3", "description": "Library to connect to XO-Server", "keywords": [ "xen", From d279db2a0eaba2d4eb608cfa12cd59c72ad27bed Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 28 Oct 2015 12:42:20 +0100 Subject: [PATCH 100/134] Update deps. --- packages/xo-lib/api.js | 8 ++++---- packages/xo-lib/package.json | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js index 783fd5d09..351167260 100644 --- a/packages/xo-lib/api.js +++ b/packages/xo-lib/api.js @@ -6,8 +6,8 @@ var Bluebird = require('bluebird') var EventEmitter = require('events').EventEmitter var eventToPromise = require('event-to-promise') var inherits = require('util').inherits -var jsonRpc = require('@julien-f/json-rpc') -var MethodNotFound = require('@julien-f/json-rpc/errors').MethodNotFound +var MethodNotFound = require('json-rpc-peer').MethodNotFound +var Peer = require('json-rpc-peer').default var startsWith = require('lodash.startswith') var WebSocket = require('ws') @@ -60,7 +60,7 @@ function Api (url) { // The JSON-RPC server. var this_ = this - this._jsonRpc = jsonRpc.createServer(function (message) { + this._jsonRpc = new Peer(function (message) { if (message.type === 'notification') { this_.emit('notification', message) } else { @@ -68,7 +68,7 @@ function Api (url) { throw new MethodNotFound(message.method) } }).on('data', function (message) { - this_._socket.send(JSON.stringify(message)) + this_._socket.send(message) }) } inherits(Api, EventEmitter) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 3f371422e..e6860f2e3 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -19,25 +19,27 @@ "node": ">=0.8.0" }, "scripts": { - "test": "standard && mocha '*.spec.js'" + "lint": "standard", + "posttest": "npm run lint", + "test": "mocha \"*.spec.js\"" }, "dependencies": { - "@julien-f/json-rpc": "^0.3.5", "bluebird": "^2.9.6", - "event-to-promise": "^0.3.3", + "event-to-promise": "^0.4.0", "exec-promise": "^0.5.1", + "json-rpc-peer": "^0.11.0", "lodash.assign": "^3.0.0", "lodash.foreach": "^3.0.1", "lodash.isstring": "^3.0.0", "lodash.startswith": "^3.0.0", - "make-error": "^0.3.0", + "make-error": "^1.0.4", "pw": "0.0.4", "ws": "^0.8.0", - "xo-collection": "^0.3.1" + "xo-collection": "^0.4.0" }, "devDependencies": { "mocha": "^2.1.0", - "must": "^0.12.0", + "must": "^0.13.1", "standard": "*" } } From 9122f9b291bac7d7a4904a6b22bfa5e9a95cf20e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 28 Oct 2015 12:46:51 +0100 Subject: [PATCH 101/134] Always use session.signIn(). --- packages/xo-lib/xo.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index c684d6838..dc7dbe6c1 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -240,12 +240,7 @@ Xo.prototype._tryToOpenSession = function () { return } - this._api.call( - credentials.token ? - 'session.signInWithToken' : - 'session.signInWithPassword', - credentials - ).bind(this).then( + this._api.call('session.signIn', credentials).bind(this).then( function (user) { this.user = user From 3a0413d8bb263b8350e7a41ba7d3ab814efab407 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 28 Oct 2015 12:54:52 +0100 Subject: [PATCH 102/134] Fix coding style. --- packages/xo-lib/package.json | 2 +- packages/xo-lib/xo.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index e6860f2e3..6051f7d7e 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -40,6 +40,6 @@ "devDependencies": { "mocha": "^2.1.0", "must": "^0.13.1", - "standard": "*" + "standard": "^5.3.1" } } diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index dc7dbe6c1..6f92ed583 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -138,9 +138,9 @@ function Xo (opts) { return } - var method = notification.params.type === 'exit' ? - unsetMultiple : - setMultiple + var method = notification.params.type === 'exit' + ? unsetMultiple + : setMultiple method(this.objects, notification.params.items) }.bind(this)) @@ -207,13 +207,13 @@ Xo.prototype.signOut = function () { signIn.reject(new SessionError('sign in aborted')) } - return this.status === 'connected' ? + return this.status === 'connected' // Attempt to sign out and ignore any return values and errors. - this._api.call('session.signOut').then(noop, noop) : + ? this._api.call('session.signOut').then(noop, noop) // Always return a promise. - Bluebird.resolve() + : Bluebird.resolve() } Xo.prototype._connect = function _connect () { From f5d790b26451b923a8edf21574d59215c19d78ef Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 30 Dec 2015 11:40:56 +0100 Subject: [PATCH 103/134] Do not automatically retry on connection errors! --- packages/xo-lib/xo.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js index 6f92ed583..336865d77 100644 --- a/packages/xo-lib/xo.js +++ b/packages/xo-lib/xo.js @@ -170,7 +170,7 @@ function Xo (opts) { this._connect() } -Xo.prototype.call = function (method, params) { +Xo.prototype.call = function (method, params, retryOnConnectionError) { // Prevent session.*() from being because it may interfere // with this class session management. if (startsWith(method, 'session.')) { @@ -179,11 +179,19 @@ Xo.prototype.call = function (method, params) { ) } - return this._session.bind(this).then(function () { - return this._api.call(method, params) - }).catch(ConnectionError, SessionError, function () { - // Automatically requeue this call. - return this.call(method, params) + var this_ = this + return this._session.then(function () { + return this_._api.call(method, params) + }).catch(function (error) { + if ( + error instanceof SessionError || + retryOnConnectionError && error instanceof ConnectionError + ) { + // Automatically requeue this call. + return this_.call(method, params) + } + + throw error }) } From 553fc6f5d962b71825f767d5eda7c1bc780b173f Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 30 Dec 2015 12:01:12 +0100 Subject: [PATCH 104/134] 0.7.4 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 6051f7d7e..090b36890 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.7.3", + "version": "0.7.4", "description": "Library to connect to XO-Server", "keywords": [ "xen", From 0b143b580a9484c8ddbec8efa8f2397eaaeaa7a3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 7 Jan 2016 17:30:53 +0100 Subject: [PATCH 105/134] Various updates. --- packages/xo-lib/.babelrc | 8 + packages/xo-lib/.editorconfig | 61 +++++- packages/xo-lib/.gitignore | 10 +- packages/xo-lib/.mocha.js | 5 + packages/xo-lib/.mocha.opts | 1 + packages/xo-lib/.npmignore | 9 + packages/xo-lib/.travis.yml | 9 +- packages/xo-lib/api.js | 151 --------------- packages/xo-lib/back-off.js | 76 -------- packages/xo-lib/cli.js | 53 ------ packages/xo-lib/connection-error.js | 9 - packages/xo-lib/fix-url.js | 21 --- packages/xo-lib/fix-url.spec.js | 48 ----- packages/xo-lib/index.js | 7 - packages/xo-lib/package.json | 60 ++++-- packages/xo-lib/session-error.js | 9 - packages/xo-lib/src/index.js | 63 +++++++ packages/xo-lib/xo.js | 282 ---------------------------- 18 files changed, 199 insertions(+), 683 deletions(-) create mode 100644 packages/xo-lib/.babelrc create mode 100644 packages/xo-lib/.mocha.js create mode 100644 packages/xo-lib/.mocha.opts delete mode 100644 packages/xo-lib/api.js delete mode 100644 packages/xo-lib/back-off.js delete mode 100755 packages/xo-lib/cli.js delete mode 100644 packages/xo-lib/connection-error.js delete mode 100644 packages/xo-lib/fix-url.js delete mode 100644 packages/xo-lib/fix-url.spec.js delete mode 100644 packages/xo-lib/index.js delete mode 100644 packages/xo-lib/session-error.js create mode 100644 packages/xo-lib/src/index.js delete mode 100644 packages/xo-lib/xo.js diff --git a/packages/xo-lib/.babelrc b/packages/xo-lib/.babelrc new file mode 100644 index 000000000..5e4035751 --- /dev/null +++ b/packages/xo-lib/.babelrc @@ -0,0 +1,8 @@ +{ + "comments": false, + "compact": true, + "presets": [ + "stage-0", + "es2015" + ] +} diff --git a/packages/xo-lib/.editorconfig b/packages/xo-lib/.editorconfig index e35119a31..da21ef4c5 100644 --- a/packages/xo-lib/.editorconfig +++ b/packages/xo-lib/.editorconfig @@ -1,12 +1,65 @@ -# EditorConfig is awesome: http://EditorConfig.org +# http://EditorConfig.org +# +# Julien Fontanet's configuration +# https://gist.github.com/julien-f/8096213 -# top-most EditorConfig file +# Top-most EditorConfig file. root = true +# Common config. [*] charset = utf-8 end_of_line = lf -indent_size = 2 -indent_style = space insert_final_newline = true trim_trailing_whitespaces = true + +# CoffeeScript +# +# https://github.com/polarmobile/coffeescript-style-guide/blob/master/README.md +[*.{,lit}coffee] +indent_size = 2 +indent_style = space + +# Markdown +[*.{md,mdwn,mdown,markdown}] +indent_size = 4 +indent_style = space + +# Package.json +# +# This indentation style is the one used by npm. +[/package.json] +indent_size = 2 +indent_style = space + +# Jade +[*.jade] +indent_size = 2 +indent_style = space + +# JavaScript +# +# Two spaces seems to be the standard most common style, at least in +# Node.js (http://nodeguide.com/style.html#tabs-vs-spaces). +[*.js] +indent_size = 2 +indent_style = space + +# Less +[*.less] +indent_size = 2 +indent_style = space + +# Sass +# +# Style used for http://libsass.com +[*.s[ac]ss] +indent_size = 2 +indent_style = space + +# YAML +# +# Only spaces are allowed. +[*.yaml] +indent_size = 2 +indent_style = space diff --git a/packages/xo-lib/.gitignore b/packages/xo-lib/.gitignore index 2ccbe4656..6959be1cf 100644 --- a/packages/xo-lib/.gitignore +++ b/packages/xo-lib/.gitignore @@ -1 +1,9 @@ -/node_modules/ +/.nyc_output/ +/bower_components/ +/dist/ + +npm-debug.log +npm-debug.log.* + +!node_modules/* +node_modules/*/ diff --git a/packages/xo-lib/.mocha.js b/packages/xo-lib/.mocha.js new file mode 100644 index 000000000..e6d84e403 --- /dev/null +++ b/packages/xo-lib/.mocha.js @@ -0,0 +1,5 @@ +Error.stackTraceLimit = 100 + +try { require('trace') } catch (_) {} +try { require('clarify') } catch (_) {} +try { require('source-map-support/register') } catch (_) {} diff --git a/packages/xo-lib/.mocha.opts b/packages/xo-lib/.mocha.opts new file mode 100644 index 000000000..6cfd94898 --- /dev/null +++ b/packages/xo-lib/.mocha.opts @@ -0,0 +1 @@ +--require ./.mocha.js diff --git a/packages/xo-lib/.npmignore b/packages/xo-lib/.npmignore index 889f827ca..c31ee82cb 100644 --- a/packages/xo-lib/.npmignore +++ b/packages/xo-lib/.npmignore @@ -1 +1,10 @@ +/examples/ +example.js +example.js.map +*.example.js +*.example.js.map + +/test/ +/tests/ *.spec.js +*.spec.js.map diff --git a/packages/xo-lib/.travis.yml b/packages/xo-lib/.travis.yml index 427dd41f8..502095fce 100644 --- a/packages/xo-lib/.travis.yml +++ b/packages/xo-lib/.travis.yml @@ -1,5 +1,10 @@ language: node_js node_js: + - 'stable' + - '4' + - '0.12' - '0.10' - - '0.11' - - iojs + +# Use containers. +# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false diff --git a/packages/xo-lib/api.js b/packages/xo-lib/api.js deleted file mode 100644 index 351167260..000000000 --- a/packages/xo-lib/api.js +++ /dev/null @@ -1,151 +0,0 @@ -'use strict' - -// =================================================================== - -var Bluebird = require('bluebird') -var EventEmitter = require('events').EventEmitter -var eventToPromise = require('event-to-promise') -var inherits = require('util').inherits -var MethodNotFound = require('json-rpc-peer').MethodNotFound -var Peer = require('json-rpc-peer').default -var startsWith = require('lodash.startswith') -var WebSocket = require('ws') - -var ConnectionError = require('./connection-error') -var fixUrl = require('./fix-url') - -// =================================================================== - -function getCurrentUrl () { - /* global window: false */ - - if (typeof window === 'undefined') { - throw new Error('cannot get current URL') - } - - return String(window.location) -} - -function makeDeferred () { - var resolve, reject - var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_ - reject = reject_ - }) - - return { - promise: promise, - reject: reject, - resolve: resolve - } -} - -function noop () {} - -// ------------------------------------------------------------------- - -// Low level interface to XO. -function Api (url) { - // Super constructor. - EventEmitter.call(this) - - // Fix the URL (ensure correct protocol and /api/ path). - this._url = fixUrl(url || getCurrentUrl()) - - // Will contains the connection promise. - this._connection = null - - // Will contains the WebSocket. - this._socket = null - - // The JSON-RPC server. - var this_ = this - this._jsonRpc = new Peer(function (message) { - if (message.type === 'notification') { - this_.emit('notification', message) - } else { - // This object does not support requests. - throw new MethodNotFound(message.method) - } - }).on('data', function (message) { - this_._socket.send(message) - }) -} -inherits(Api, EventEmitter) - -Api.prototype.close = function () { - var socket = this._socket - if (socket) { - socket.close() - - console.log(socket.readyState) - if (socket.readyState !== 3) { - return eventToPromise(socket, 'close').then(noop) - } - } - - return Bluebird.resolve() -} - -Api.prototype.connect = function () { - if (this._connection) { - return this._connection - } - - var deferred = makeDeferred() - this._connection = deferred.promise - - var opts = {} - if (startsWith(this._url, 'wss')) { - // Due to imperfect TLS implementation in XO-Server. - opts.rejectUnauthorized = false - } - var socket = this._socket = new WebSocket(this._url, '', opts) - - // Used to avoid binding listeners to this object. - var this_ = this - - // When the socket opens, send any queued requests. - socket.addEventListener('open', function () { - // Resolves the promise. - deferred.resolve() - - this_.emit('connected') - }) - - socket.addEventListener('error', function (error) { - this_._connection = null - this_._socket = null - - // Fails the connect promise if possible. - deferred.reject(error) - }) - - socket.addEventListener('message', function (message) { - this_._jsonRpc.write(message.data) - }) - - socket.addEventListener('close', function () { - this_._connection = null - this_._socket = null - - this_._jsonRpc.failPendingRequests(new ConnectionError()) - - // Only emit this event if connected before. - if (deferred.promise.isFulfilled()) { - this_.emit('disconnected') - } - }) - - return deferred.promise -} - -Api.prototype.call = function (method, params) { - var jsonRpc = this._jsonRpc - - return this.connect().then(function () { - return jsonRpc.request(method, params) - }) -} - -module.exports = Api diff --git a/packages/xo-lib/back-off.js b/packages/xo-lib/back-off.js deleted file mode 100644 index d79610c31..000000000 --- a/packages/xo-lib/back-off.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict' - -// =================================================================== - -var Bluebird = require('bluebird') - -// =================================================================== - -function returnThis () { - /* jshint validthis: true */ - - return this -} - -// Returns an iterator to the Fibonacci sequence. -function fibonacci (start) { - var prev = 0 - var curr = start || 1 - - var iterator = { - next: function () { - var tmp = curr - curr += prev - prev = tmp - - return { - done: false, - value: prev - } - } - } - - // Make the iterator a true iterable (ES6). - if (typeof Symbol !== 'undefined') { - iterator[Symbol.iterator] = returnThis - } - - return iterator -} - -// =================================================================== - -function defaultGenerator () { - return fibonacci(1e3) -} - -function BackOff (opts) { - if (!opts) { - opts = {} - } - - this._attempts = 0 - this._generator = opts.generator || defaultGenerator - this._iterator = this._generator() - this._maxAttempts = opts.maxAttempts || Infinity -} - -BackOff.prototype.wait = function () { - var maxAttempts = this._maxAttempts - if (this._attempts++ > maxAttempts) { - return Bluebird.reject(new Error( - 'maximum attempts reached (' + maxAttempts + ')' - )) - } - - return Bluebird.delay(this._iterator.next().value) -} - -BackOff.prototype.reset = function () { - this._attempts = 0 - this._iterator = this._generator() -} - -// =================================================================== - -module.exports = BackOff diff --git a/packages/xo-lib/cli.js b/packages/xo-lib/cli.js deleted file mode 100755 index 9c1af26fd..000000000 --- a/packages/xo-lib/cli.js +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node - -'use strict' - -var Bluebird = require('bluebird') -var createRepl = require('repl').start -var eventToPromise = require('event-to-promise') -var pw = require('pw') - -var Xo = require('./').Xo - -// =================================================================== - -var usage = '' - -function main (args) { - if (args[0] === '--help' || args[0] === 'h') { - return usage - } - - var xo = new Xo(args[0]) - - return new Bluebird(function (resolve) { - process.stdout.write('Password: ') - pw(resolve) - }).then(function (password) { - return xo.signIn({ - email: args[1], - password: password - }) - }).then(function () { - var repl = createRepl({}) - repl.context.xo = xo - - // Make the REPL waits for promise completion. - var evaluate = Bluebird.promisify(repl.eval) - repl.eval = function (cmd, context, filename, cb) { - evaluate(cmd, context, filename) - // See https://github.com/petkaantonov/bluebird/issues/594 - .then(function (result) { return result }) - .nodeify(cb) - } - - return eventToPromise(repl, 'exit') - }) -} -module.exports = main - -// =================================================================== - -if (!module.parent) { - require('exec-promise')(main) -} diff --git a/packages/xo-lib/connection-error.js b/packages/xo-lib/connection-error.js deleted file mode 100644 index a0f6ab1a0..000000000 --- a/packages/xo-lib/connection-error.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict' - -// =================================================================== - -var makeError = require('make-error') - -// =================================================================== - -module.exports = makeError('ConnectionError') diff --git a/packages/xo-lib/fix-url.js b/packages/xo-lib/fix-url.js deleted file mode 100644 index d767d5589..000000000 --- a/packages/xo-lib/fix-url.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -// =================================================================== - -// Fix URL if necessary. -var URL_RE = /^(?:(?:http|ws)(s)?:\/\/)?(.*?)\/*(?:\/api\/)?(\?.*?)?(?:#.*)?$/ -function fixUrl (url) { - var matches = URL_RE.exec(url) - var isSecure = !!matches[1] - var hostAndPath = matches[2] - var search = matches[3] - - return [ - isSecure ? 'wss' : 'ws', - '://', - hostAndPath, - '/api/', - search - ].join('') -} -module.exports = fixUrl diff --git a/packages/xo-lib/fix-url.spec.js b/packages/xo-lib/fix-url.spec.js deleted file mode 100644 index 2ff751050..000000000 --- a/packages/xo-lib/fix-url.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict' - -// =================================================================== - -var expect = require('must') - -// =================================================================== - -/* eslint-env mocha */ - -describe('fixUrl()', function () { - var fixUrl = require('./fix-url') - - describe('protocol', function () { - it('is added if missing', function () { - expect(fixUrl('localhost/api/')).to.equal('ws://localhost/api/') - }) - - it('HTTP(s) is converted to WS(s)', function () { - expect(fixUrl('http://localhost/api/')).to.equal('ws://localhost/api/') - expect(fixUrl('https://localhost/api/')).to.equal('wss://localhost/api/') - }) - - it('is not added if already present', function () { - expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/') - expect(fixUrl('wss://localhost/api/')).to.equal('wss://localhost/api/') - }) - }) - - describe('/api/ path', 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/') - }) - - it('is not added if already present', function () { - expect(fixUrl('ws://localhost/api/')).to.equal('ws://localhost/api/') - }) - - it('removes the hash part', function () { - expect(fixUrl('ws://localhost/#foo')).to.equal('ws://localhost/api/') - }) - - it('conserve the search part', function () { - expect(fixUrl('ws://localhost/?foo')).to.equal('ws://localhost/api/?foo') - }) - }) -}) diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js deleted file mode 100644 index 695713bfd..000000000 --- a/packages/xo-lib/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -// Expose Bluebird for now to ease integration (e.g. with Angular.js). -exports.setScheduler = require('bluebird').setScheduler - -exports.Api = require('./api') -exports.Xo = require('./xo') diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 090b36890..f1cbf68d2 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,12 +1,15 @@ { "name": "xo-lib", "version": "0.7.4", + "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ "xen", "orchestra", "xen-orchestra" ], + "homepage": "https://github.com/vatesfr/xo-lib", + "bugs": "https://github.com/vatesfr/xo-lib/issues", "repository": { "type": "git", "url": "https://github.com/vatesfr/xo-lib" @@ -15,31 +18,48 @@ "name": "Julien Fontanet", "email": "julien.fontanet@vates.fr" }, + "preferGlobal": false, + "main": "dist/", + "bin": {}, + "files": [ + "dist/" + ], "engines": { - "node": ">=0.8.0" - }, - "scripts": { - "lint": "standard", - "posttest": "npm run lint", - "test": "mocha \"*.spec.js\"" + "node": ">=0.12" }, "dependencies": { - "bluebird": "^2.9.6", - "event-to-promise": "^0.4.0", - "exec-promise": "^0.5.1", - "json-rpc-peer": "^0.11.0", - "lodash.assign": "^3.0.0", - "lodash.foreach": "^3.0.1", - "lodash.isstring": "^3.0.0", - "lodash.startswith": "^3.0.0", - "make-error": "^1.0.4", - "pw": "0.0.4", - "ws": "^0.8.0", - "xo-collection": "^0.4.0" + "jsonrpc-websocket-client": "0.0.1-3", + "lodash.startswith": "^3.0.1", + "make-error": "^1.0.4" }, "devDependencies": { - "mocha": "^2.1.0", + "babel-cli": "^6.3.17", + "babel-eslint": "^5.0.0-beta6", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "clarify": "^1.0.5", + "dependency-check": "^2.5.1", + "mocha": "^2.3.4", "must": "^0.13.1", - "standard": "^5.3.1" + "nyc": "^5.0.0", + "source-map-support": "^0.4.0", + "standard": "^5.4.1", + "trace": "^2.0.1" + }, + "scripts": { + "build": "babel --source-maps --out-dir=dist/ src/", + "dev": "babel --watch --source-maps --out-dir=dist/ src/", + "dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"", + "lint": "standard", + "depcheck": "dependency-check ./package.json", + "posttest": "npm run lint && npm run depcheck", + "prepublish": "npm run build", + "test": "nyc mocha --opts .mocha.opts \"dist/**/*.spec.js\"" + }, + "standard": { + "ignore": [ + "dist/**" + ], + "parser": "babel-eslint" } } diff --git a/packages/xo-lib/session-error.js b/packages/xo-lib/session-error.js deleted file mode 100644 index 19c54f400..000000000 --- a/packages/xo-lib/session-error.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict' - -// =================================================================== - -var makeError = require('make-error') - -// =================================================================== - -module.exports = makeError('SessionError') diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js new file mode 100644 index 000000000..81dd75cac --- /dev/null +++ b/packages/xo-lib/src/index.js @@ -0,0 +1,63 @@ +import startsWith from 'lodash.startswith' +import JsonRpcWebSocketClient, { + OPEN, + CLOSED +} from 'jsonrpc-websocket-client' +import { BaseError } from 'make-error' + +// =================================================================== + +const noop = () => {} + +// =================================================================== + +export class XoError extends BaseError {} + +// ------------------------------------------------------------------- + +// TODO: implement call(...).retry(predicate) +export default class Xo extends JsonRpcWebSocketClient { + constructor (opts) { + super(`${opts && opts.url || '.' }/api/`) + + this._credentials = opts && opts.credentials || null + this._user = null + + this.on(OPEN, () => { + if (this._credentials) { + this._signIn(this._credentials).catch(noop) + } + }) + this.on(CLOSED, () => { + this._user = null + }) + } + + get user () { + return this._user + } + + call (method, args) { + return new Promise(resolve => { + if (startsWith(method, 'session.')) { + throw new XoError('session.*() methods are disabled from this interface') + } + + resolve(super.call(method, args)) + }) + } + + signIn (credentials) { + // Register this credentials for future use. + this._credentials = credentials + + return this._signIn(credentials) + } + + _signIn (credentials) { + return super.call('session.signIn', credentials).then(user => { + this._user = user + this.emit('authenticated') + }) + } +} diff --git a/packages/xo-lib/xo.js b/packages/xo-lib/xo.js deleted file mode 100644 index 336865d77..000000000 --- a/packages/xo-lib/xo.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict' - -// =================================================================== - -var Bluebird = require('bluebird') -var Collection = require('xo-collection').default -var forEach = require('lodash.foreach') -var Index = require('xo-collection/index') -var isString = require('lodash.isstring') -var startsWith = require('lodash.startswith') -var UniqueIndex = require('xo-collection/unique-index') - -var Api = require('./api') -var BackOff = require('./back-off') -var ConnectionError = require('./connection-error') -var SessionError = require('./session-error') - -// =================================================================== - -// function bind (fn, thisArg) { -// if (!fn) { -// return fn -// } - -// return function () { -// return fn.apply(thisArg, arguments) -// } -// } - -function makeStandaloneDeferred () { - var resolve, reject - - var promise = new Bluebird(function (resolve_, reject_) { - resolve = resolve_ - reject = reject_ - }) - promise.resolve = resolve - promise.reject = reject - - return promise -} - -function noop () {} - -// var trace = -// bind(console.trace, console) || -// bind(console.log, console) || -// noop - -// ------------------------------------------------------------------- - -var defineProperty = Object.defineProperty - -function getDeprecatedUUID () { - // trace('.UUID is deprecated, use .id instead') - - return this.id -} - -function defineDeprecatedUUID (object) { - defineProperty(object, 'UUID', { - get: getDeprecatedUUID - }) -} - -// var LINK_RE = /^(.*)\$link\$$/ -// function createAutoLinks (collection, object) { -// var all = collection.all - -// forEach(object, function resolveObject (value, key, object) { -// var matches = key.match(LINK_RE) -// if (!matches) { -// return -// } - -// defineProperty(object, matches[1], { -// get: function () { -// return all[value] -// } -// }) -// }) -// } - -function setMultiple (collection, items) { - var messages = collection.indexes.messagesByObject - - forEach(items, function (item, key) { - defineDeprecatedUUID(item) - // createAutoLinks(collection, item) - - defineProperty(item, 'messages', { - get: function () { - return messages[key] - } - }) - - collection.set(key, item) - }) -} - -function unsetMultiple (collection, items) { - forEach(items, function (_, key) { - if (collection.has(key)) { - collection.remove(key) - } - }) -} - -// =================================================================== - -function Xo (opts) { - if (!opts) { - opts = {} - } else if (isString(opts)) { - opts = { - url: opts - } - } - - // ----------------------------------------------------------------- - - var api = new Api(opts.url) - - api.on('connected', function () { - this._backOff.reset() - this.status = 'connected' - - this._tryToOpenSession() - }.bind(this)) - - api.on('disconnected', function () { - this._closeSession() - this._connect() - }.bind(this)) - - api.on('notification', function (notification) { - if (notification.method !== 'all') { - return - } - - var method = notification.params.type === 'exit' - ? unsetMultiple - : setMultiple - - method(this.objects, notification.params.items) - }.bind(this)) - - // ----------------------------------------------------------------- - - var objects = this.objects = new Collection() - objects.createIndex('ref', new UniqueIndex('ref')) - objects.createIndex('type', new Index('type')) - objects.createIndex('messagesByObject', new Index(function (obj) { - if (obj.type === 'message') { - return obj.$object - } - })) - - this.status = 'disconnected' - this.user = null - - this._api = api - this._backOff = new BackOff() - this._credentials = opts.creadentials - this._session = makeStandaloneDeferred() - this._signIn = null - - // ----------------------------------------------------------------- - - this._connect() -} - -Xo.prototype.call = function (method, params, retryOnConnectionError) { - // Prevent session.*() from being because it may interfere - // with this class session management. - if (startsWith(method, 'session.')) { - return Bluebird.reject( - new Error('session.*() methods are disabled from this interface') - ) - } - - var this_ = this - return this._session.then(function () { - return this_._api.call(method, params) - }).catch(function (error) { - if ( - error instanceof SessionError || - retryOnConnectionError && error instanceof ConnectionError - ) { - // Automatically requeue this call. - return this_.call(method, params) - } - - throw error - }) -} - -Xo.prototype.signIn = function (credentials) { - this.signOut() - - this._credentials = credentials - this._signIn = makeStandaloneDeferred() - - this._tryToOpenSession() - - return this._signIn -} - -Xo.prototype.signOut = function () { - this._closeSession() - this._credentials = null - - var signIn = this._signIn - if (signIn && signIn.isPending()) { - signIn.reject(new SessionError('sign in aborted')) - } - - return this.status === 'connected' - - // Attempt to sign out and ignore any return values and errors. - ? this._api.call('session.signOut').then(noop, noop) - - // Always return a promise. - : Bluebird.resolve() -} - -Xo.prototype._connect = function _connect () { - this.status = 'connecting' - - return this._api.connect().bind(this).catch(function (error) { - console.warn('could not connect:', error) - - return this._backOff.wait().bind(this).then(_connect) - }) -} - -Xo.prototype._closeSession = function () { - if (!this._session.isPending()) { - this._session = makeStandaloneDeferred() - } - - this.user = null -} - -Xo.prototype._tryToOpenSession = function () { - var credentials = this._credentials - if (!credentials || this.status !== 'connected') { - return - } - - this._api.call('session.signIn', credentials).bind(this).then( - function (user) { - this.user = user - - this._api.call('xo.getAllObjects').bind(this).then(function (objects) { - this.objects.clear() - setMultiple(this.objects, objects) - }) - - // Validate the sign in. - var signIn = this._signIn - if (signIn) { - signIn.resolve() - } - - // Open the session. - this._session.resolve() - }, - - function (error) { - // Reject the sign in. - var signIn = this._signIn - if (signIn) { - signIn.reject(error) - } - } - ) -} - -// =================================================================== - -module.exports = Xo From c8a6fd19a70b6470c6f25293a76a60dd1ae68042 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 7 Jan 2016 18:04:52 +0100 Subject: [PATCH 106/134] 0.8.0-0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index f1cbf68d2..c0b0cedf7 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.7.4", + "version": "0.8.0-0", "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ From 749d5e22bb3dd4daef3fe35570c3309e885b0ae3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 Jan 2016 17:46:38 +0100 Subject: [PATCH 107/134] Update jsonrpc-websocket-client to 0.0.1-4. --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index c0b0cedf7..1f87b44ee 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -28,7 +28,7 @@ "node": ">=0.12" }, "dependencies": { - "jsonrpc-websocket-client": "0.0.1-3", + "jsonrpc-websocket-client": "0.0.1-4", "lodash.startswith": "^3.0.1", "make-error": "^1.0.4" }, From 85d0271b86c052f1cbc9625fc3cbc73b3e8f1471 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 Jan 2016 17:47:35 +0100 Subject: [PATCH 108/134] Xo#call(method, args).retry(predicate) --- packages/xo-lib/src/index.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index 81dd75cac..a2ff93e4f 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -37,14 +37,22 @@ export default class Xo extends JsonRpcWebSocketClient { return this._user } - call (method, args) { - return new Promise(resolve => { - if (startsWith(method, 'session.')) { - throw new XoError('session.*() methods are disabled from this interface') - } + call (method, args, i) { + if (startsWith(method, 'session.')) { + return Promise.reject( + new XoError('session.*() methods are disabled from this interface') + ) + } - resolve(super.call(method, args)) + const promise = super.call(method, args) + promise.retry = predicate => promise.catch(error => { + i = (i || 0) + 1 + if (predicate(error, i)) { + return this.call(method, args, i) + } }) + + return promise } signIn (credentials) { From 7d7cc565277369dfe26db4c0eff3426b33edf07e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 22 Jan 2016 17:47:41 +0100 Subject: [PATCH 109/134] 0.8.0-1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 1f87b44ee..ccb869c65 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.8.0-0", + "version": "0.8.0-1", "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ From cbfb94afcbfd22d2027712c9f4b8eaa52ce7bfc3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 15 Mar 2016 15:42:14 +0100 Subject: [PATCH 110/134] Update deps. --- packages/xo-lib/package.json | 10 +++++----- packages/xo-lib/src/index.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index ccb869c65..ee6cadc1f 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -28,22 +28,22 @@ "node": ">=0.12" }, "dependencies": { - "jsonrpc-websocket-client": "0.0.1-4", - "lodash.startswith": "^3.0.1", + "jsonrpc-websocket-client": "0.0.1-5", + "lodash.startswith": "^4.0.0", "make-error": "^1.0.4" }, "devDependencies": { "babel-cli": "^6.3.17", - "babel-eslint": "^5.0.0-beta6", + "babel-eslint": "^6.0.0-beta.6", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "clarify": "^1.0.5", "dependency-check": "^2.5.1", "mocha": "^2.3.4", "must": "^0.13.1", - "nyc": "^5.0.0", + "nyc": "^6.1.1", "source-map-support": "^0.4.0", - "standard": "^5.4.1", + "standard": "^6.0.8", "trace": "^2.0.1" }, "scripts": { diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index a2ff93e4f..a4d9f482a 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -18,7 +18,7 @@ export class XoError extends BaseError {} // TODO: implement call(...).retry(predicate) export default class Xo extends JsonRpcWebSocketClient { constructor (opts) { - super(`${opts && opts.url || '.' }/api/`) + super(`${opts && opts.url || '.'}/api/`) this._credentials = opts && opts.credentials || null this._user = null @@ -45,7 +45,7 @@ export default class Xo extends JsonRpcWebSocketClient { } const promise = super.call(method, args) - promise.retry = predicate => promise.catch(error => { + promise.retry = (predicate) => promise.catch((error) => { i = (i || 0) + 1 if (predicate(error, i)) { return this.call(method, args, i) @@ -63,7 +63,7 @@ export default class Xo extends JsonRpcWebSocketClient { } _signIn (credentials) { - return super.call('session.signIn', credentials).then(user => { + return super.call('session.signIn', credentials).then((user) => { this._user = user this.emit('authenticated') }) From fe7a9104a8b560fd8b91003ca3d95c59be88e4da Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 15 Mar 2016 16:13:49 +0100 Subject: [PATCH 111/134] Minor package.json changes. --- packages/xo-lib/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index ee6cadc1f..ca851d681 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -47,11 +47,11 @@ "trace": "^2.0.1" }, "scripts": { - "build": "babel --source-maps --out-dir=dist/ src/", + "build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "depcheck": "dependency-check ./package.json", "dev": "babel --watch --source-maps --out-dir=dist/ src/", "dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"", "lint": "standard", - "depcheck": "dependency-check ./package.json", "posttest": "npm run lint && npm run depcheck", "prepublish": "npm run build", "test": "nyc mocha --opts .mocha.opts \"dist/**/*.spec.js\"" From 3527b86ec5fc67bdc20f338de233af935fb54dd3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 15 Mar 2016 16:25:46 +0100 Subject: [PATCH 112/134] Add empty test file. --- packages/xo-lib/src/index.spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/xo-lib/src/index.spec.js diff --git a/packages/xo-lib/src/index.spec.js b/packages/xo-lib/src/index.spec.js new file mode 100644 index 000000000..2319bd7d5 --- /dev/null +++ b/packages/xo-lib/src/index.spec.js @@ -0,0 +1,17 @@ +/* eslint-env mocha */ + +import expect from 'must' + +// =================================================================== + +import myLib from './' + +// =================================================================== + +describe.skip('myLib', () => { + it('does something', () => { + // TODO: some real tests. + + expect(myLib).to.exists() + }) +}) From c96d94329e1dcac2b12a0d437fd310cfd73872f2 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 30 Mar 2016 11:34:26 +0200 Subject: [PATCH 113/134] Fix using / as url. --- packages/xo-lib/src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index a4d9f482a..7a5274638 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -18,7 +18,8 @@ export class XoError extends BaseError {} // TODO: implement call(...).retry(predicate) export default class Xo extends JsonRpcWebSocketClient { constructor (opts) { - super(`${opts && opts.url || '.'}/api/`) + const url = opts && opts.url || '.' + super(`${url === '/' ? '' : url}/api/`) this._credentials = opts && opts.credentials || null this._user = null From 538025edd5cfd4cdaa89fe4e8830a567c3444b5e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 Apr 2016 15:14:33 +0200 Subject: [PATCH 114/134] chore: remove irrelevant comment --- packages/xo-lib/src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index 7a5274638..67aee4ce1 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -15,7 +15,6 @@ export class XoError extends BaseError {} // ------------------------------------------------------------------- -// TODO: implement call(...).retry(predicate) export default class Xo extends JsonRpcWebSocketClient { constructor (opts) { const url = opts && opts.url || '.' From 840f0b6379dd909ecaa850aac5ed4ca3eb3821c9 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 Apr 2016 15:18:09 +0200 Subject: [PATCH 115/134] chore(package): update babel-eslint to version 6.0.4 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index ca851d681..06168451e 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -34,7 +34,7 @@ }, "devDependencies": { "babel-cli": "^6.3.17", - "babel-eslint": "^6.0.0-beta.6", + "babel-eslint": "^6.0.4", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "clarify": "^1.0.5", From a4dc965c236d3b041454b975bf0a8dfbe2f1fc96 Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Tue, 3 May 2016 09:16:47 +0200 Subject: [PATCH 116/134] chore(package): update standard to version 7.0.0 (#3) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 06168451e..2f70b37bf 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -43,7 +43,7 @@ "must": "^0.13.1", "nyc": "^6.1.1", "source-map-support": "^0.4.0", - "standard": "^6.0.8", + "standard": "^7.0.0", "trace": "^2.0.1" }, "scripts": { From 306d5d8fc7c9b261030c38f69bc6e8d239585a20 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 6 May 2016 12:33:56 +0200 Subject: [PATCH 117/134] feat(event): authenticationFailure --- packages/xo-lib/src/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index 67aee4ce1..cccd33a29 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -63,9 +63,15 @@ export default class Xo extends JsonRpcWebSocketClient { } _signIn (credentials) { - return super.call('session.signIn', credentials).then((user) => { - this._user = user - this.emit('authenticated') - }) + return super.call('session.signIn', credentials).then( + user => { + this._user = user + this.emit('authenticated') + }, + error => { + this.emit('authenticationFailure', error) + throw error + } + ) } } From 162a56232c848c5b789a0cfbbaa58dd093da167d Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Fri, 15 Jul 2016 11:26:53 +0200 Subject: [PATCH 118/134] chore(package): update nyc to version 7.0.0 (#8) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 2f70b37bf..c9cd88309 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -41,7 +41,7 @@ "dependency-check": "^2.5.1", "mocha": "^2.3.4", "must": "^0.13.1", - "nyc": "^6.1.1", + "nyc": "^7.0.0", "source-map-support": "^0.4.0", "standard": "^7.0.0", "trace": "^2.0.1" From f3ea8d012fe88bda101a36124adeb89fb530e6d6 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 20 Jul 2016 16:46:02 +0200 Subject: [PATCH 119/134] feat(Xo#refreshUser) --- packages/xo-lib/src/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index cccd33a29..c5d3cf736 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -55,6 +55,12 @@ export default class Xo extends JsonRpcWebSocketClient { return promise } + refreshUser () { + return super.call('session.getUser').then(user => { + return (this._user = user) + }) + } + signIn (credentials) { // Register this credentials for future use. this._credentials = credentials From 39a16f9a7fbed421bb901796166a07d9d01fb613 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 20 Jul 2016 16:46:20 +0200 Subject: [PATCH 120/134] 0.8.0 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index c9cd88309..d2d152bbd 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.8.0-1", + "version": "0.8.0", "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ From ac6d14113a915cdc988dd6ad543ef8e6f0ea8ea3 Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Tue, 2 Aug 2016 19:21:37 +0200 Subject: [PATCH 121/134] chore(package): update mocha to version 3.0.0 (#9) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index d2d152bbd..cdddfb3b5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -39,7 +39,7 @@ "babel-preset-stage-0": "^6.3.13", "clarify": "^1.0.5", "dependency-check": "^2.5.1", - "mocha": "^2.3.4", + "mocha": "^3.0.0", "must": "^0.13.1", "nyc": "^7.0.0", "source-map-support": "^0.4.0", From 6bc4bf308b7a881f15d5ecde7f1d089610ae3059 Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Sun, 14 Aug 2016 19:05:44 +0200 Subject: [PATCH 122/134] chore(package): update nyc to version 8.1.0 (#13) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index cdddfb3b5..746990ee4 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -41,7 +41,7 @@ "dependency-check": "^2.5.1", "mocha": "^3.0.0", "must": "^0.13.1", - "nyc": "^7.0.0", + "nyc": "^8.1.0", "source-map-support": "^0.4.0", "standard": "^7.0.0", "trace": "^2.0.1" From 224a79840da46717ca63896994ef587fa896bf5f Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Wed, 24 Aug 2016 18:23:09 +0200 Subject: [PATCH 123/134] chore(package): update standard to version 8.0.0 (#14) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 746990ee4..7504bd064 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -43,7 +43,7 @@ "must": "^0.13.1", "nyc": "^8.1.0", "source-map-support": "^0.4.0", - "standard": "^7.0.0", + "standard": "^8.0.0", "trace": "^2.0.1" }, "scripts": { From 98cd2746efb27bb1c4bc723d34400bac2cc5b0fa Mon Sep 17 00:00:00 2001 From: Greenkeeper Date: Tue, 27 Sep 2016 23:39:13 +0200 Subject: [PATCH 124/134] chore(package): update babel-eslint to version 7.0.0 (#15) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 7504bd064..0412e0db7 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -34,7 +34,7 @@ }, "devDependencies": { "babel-cli": "^6.3.17", - "babel-eslint": "^6.0.4", + "babel-eslint": "^7.0.0", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "clarify": "^1.0.5", From d7fe25c4fc358e93de37fa88758953a40fab93af Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 1 Nov 2016 22:01:39 +0100 Subject: [PATCH 125/134] chore(package): update dependencies (#17) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 0412e0db7..b01682a93 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -37,7 +37,7 @@ "babel-eslint": "^7.0.0", "babel-preset-es2015": "^6.3.13", "babel-preset-stage-0": "^6.3.13", - "clarify": "^1.0.5", + "clarify": "^2.0.0", "dependency-check": "^2.5.1", "mocha": "^3.0.0", "must": "^0.13.1", From 4294dfd8fe6d6d6b7d95a894831ddee66ecb8818 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 7 Nov 2016 21:20:54 +0100 Subject: [PATCH 126/134] chore(package): update dependencies (#18) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index b01682a93..64233e9c2 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -28,7 +28,7 @@ "node": ">=0.12" }, "dependencies": { - "jsonrpc-websocket-client": "0.0.1-5", + "jsonrpc-websocket-client": "0.1.1", "lodash.startswith": "^4.0.0", "make-error": "^1.0.4" }, From caba246e0b58eb0b304e3310e9627ac4e9b751e8 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 18 Nov 2016 10:46:51 +0100 Subject: [PATCH 127/134] fix(README): update doc --- packages/xo-lib/README.md | 187 ++++++++++++++++++-------------------- 1 file changed, 87 insertions(+), 100 deletions(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 789014e37..44b84da4c 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -1,14 +1,8 @@ -# xo-lib - -[![Build Status](https://img.shields.io/travis/vatesfr/xo-lib/master.svg)](http://travis-ci.org/vatesfr/xo-lib) -[![Dependency Status](https://david-dm.org/vatesfr/xo-lib/status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-lib) -[![devDependency Status](https://david-dm.org/vatesfr/xo-lib/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-lib#info=devDependencies) +# xo-lib [![Build Status](https://travis-ci.org/vatesfr/xo-lib.png?branch=master)](https://travis-ci.org/vatesfr/xo-lib) > Library to connect to XO-Server. -## Installation - -### Node & Browserify +## Install Installation of the [npm package](https://npmjs.org/package/xo-lib): @@ -19,100 +13,47 @@ npm install --save xo-lib Then require the package: ```javascript -var xoLib = require('xo-lib'); +import Xo from 'xo-lib' ``` -## High level API +## Usage -This high-level interface handles session sign-in and a cache of -remote XO objects. It also automatically reconnect and retry method -calls when necessary. +> If the URL is not provided and the current environment is a web +> browser, the location of the current page will be used. ```javascript // Connect to XO. -var xo = new xoLib.Xo('https://xo.company.tld'); +const xo = new Xo({ url: 'https://xo.company.tld' }) + +// Let's start by opening the connection. +await xo.connect() // Must sign in before being able to call any methods (all calls will // be buffered until signed in). -xo.signIn({ +await xo.signIn({ email: 'admin@admin.net', - password: 'admin', -}).then(function () { - console('signed as', xo.user); -}); + password: 'admin' +}) + +console('signed as', xo.user) ``` The credentials can also be passed directly to the constructor: ```javascript -var xo = new xoLib.Xo({ +const xo = Xo({ url: 'https://xo.company.tld', credentials: { email: 'admin@admin.net', password: 'admin', } -}); -``` +}) -> If the URL is not provided and the current environment is a web -> browser, the location of the current page will be used. +xo.open() -### Method call - -```javascript -xo.call('token.create').then(function (token) { - console.log('Token created', token); -}); -``` - -### Status - -The connection status is available through the status property which -is *disconnected*, *connecting* or *connected*. - -```javascript -console.log('%s to xo-server', xo.status); -``` - -### Current user - -Information about the user account used to sign in is available -through the `user` property. - -```javascript -console.log('Current user is', xo.user); -``` - -> This property is null when the status is not connected. - - -### XO Objects - -XO objects are cached locally in the `objects` collection. - -```javascript -// Read-only dictionary of all objects. -var allObjects = xo.objects.all; - -// Looks up a given object by its identifier. -var object = allObjects[id]; - -// Read-only dictionary of all indexes. -var indexes = xo.objects.indexes; - -// Read-only dictionary of types. -var byTypes = indexes.type; - -// Read-only view of all VMs. -var vms = byTypes.VM; -``` - -Available indexes are: `ref`, `type` and `UUID`. - -## Low level - -```javascript -var api = new xoLib.Api('https://xo.company.tld'); +xo.on('authenticated', () => { + console.log(xo.user) +}) ``` > If the URL is not provided and the current environment is a web @@ -121,48 +62,94 @@ var api = new xoLib.Api('https://xo.company.tld'); ### Connection ```javascript -api.connect().then(function () { - console.log('connected'); -}); +await xo.open() + +console.log('connected') ``` ### Disconnection ```javascript -api.close(); +xo.close() + +console.log('disconnected') ``` ### Method call ```javascript -api.call('session.signInWithPassword', { - email: 'admin@admin.net', - password: 'admin', -}).then(function (user) { - console.log('Connected as', user); -}); +const token = await xo.call('token.create') + +console.log('Token created', token) ``` -> A method call automatically trigger a connection if necessary. +### Status + +The connection status is available through the status property which +is *open*, *connecting* or *closed*. + +```javascript +console.log('%s to xo-server', xo.status) +``` + +### Current user + +Information about the user account used to sign in is available +through the `user` property. + +```javascript +console.log('Current user is', xo.user) +``` + +> This property is null when the status is not connected. ### Events ```javascript -api.on('connected', function () { - console.log('connected'); -}); +xo.on('open', () => { + console.log('connected') +}) ``` ```javascript -api.on('disconnected', function () { - console.log('disconnected'); -}); +xo.on('closed', () => { + console.log('disconnected') +}) ``` ```javascript -api.on('notification', function (notif) { - console.log('notification:', notif.method, notif.params); -}); +xo.on('notification', function (notif) { + console.log('notification:', notif.method, notif.params) +}) +``` + +```javascript +xo.on('authenticated', () => { + console.log('authenticated as', xo.user) +}) + +xo.on('authenticationFailure', () => { + console.log('failed to authenticate') +}) +``` + +## Development + +``` +# Install dependencies +> npm install + +# Run the tests +> npm test + +# Continuously compile +> npm run dev + +# Continuously run the tests +> npm run dev-test + +# Build for production (automatically called by npm install) +> npm run build ``` ## Contributions From 9d6560aecefc30262773481a526a689d4c95456a Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 18 Nov 2016 11:50:26 +0100 Subject: [PATCH 128/134] chore(package): update all dependencies --- packages/xo-lib/.babelrc | 8 ---- packages/xo-lib/.gitignore | 8 ++-- packages/xo-lib/.mocha.js | 5 --- packages/xo-lib/.mocha.opts | 1 - packages/xo-lib/.travis.yml | 7 ++-- packages/xo-lib/example.js | 61 +++++++++++++++++------------- packages/xo-lib/package.json | 63 ++++++++++++++++++++----------- packages/xo-lib/src/index.js | 2 +- packages/xo-lib/src/index.spec.js | 17 --------- 9 files changed, 84 insertions(+), 88 deletions(-) delete mode 100644 packages/xo-lib/.babelrc delete mode 100644 packages/xo-lib/.mocha.js delete mode 100644 packages/xo-lib/.mocha.opts delete mode 100644 packages/xo-lib/src/index.spec.js diff --git a/packages/xo-lib/.babelrc b/packages/xo-lib/.babelrc deleted file mode 100644 index 5e4035751..000000000 --- a/packages/xo-lib/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "comments": false, - "compact": true, - "presets": [ - "stage-0", - "es2015" - ] -} diff --git a/packages/xo-lib/.gitignore b/packages/xo-lib/.gitignore index 6959be1cf..827e4e420 100644 --- a/packages/xo-lib/.gitignore +++ b/packages/xo-lib/.gitignore @@ -1,9 +1,7 @@ -/.nyc_output/ -/bower_components/ /dist/ +/node_modules/ npm-debug.log npm-debug.log.* - -!node_modules/* -node_modules/*/ +pnpm-debug.log +pnpm-debug.log.* diff --git a/packages/xo-lib/.mocha.js b/packages/xo-lib/.mocha.js deleted file mode 100644 index e6d84e403..000000000 --- a/packages/xo-lib/.mocha.js +++ /dev/null @@ -1,5 +0,0 @@ -Error.stackTraceLimit = 100 - -try { require('trace') } catch (_) {} -try { require('clarify') } catch (_) {} -try { require('source-map-support/register') } catch (_) {} diff --git a/packages/xo-lib/.mocha.opts b/packages/xo-lib/.mocha.opts deleted file mode 100644 index 6cfd94898..000000000 --- a/packages/xo-lib/.mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---require ./.mocha.js diff --git a/packages/xo-lib/.travis.yml b/packages/xo-lib/.travis.yml index 502095fce..1a582c426 100644 --- a/packages/xo-lib/.travis.yml +++ b/packages/xo-lib/.travis.yml @@ -1,9 +1,8 @@ language: node_js node_js: - - 'stable' - - '4' - - '0.12' - - '0.10' + - stable + - 6 + - 4 # Use containers. # http://docs.travis-ci.com/user/workers/container-based-infrastructure/ diff --git a/packages/xo-lib/example.js b/packages/xo-lib/example.js index d87727dec..77c83ec1a 100644 --- a/packages/xo-lib/example.js +++ b/packages/xo-lib/example.js @@ -1,36 +1,45 @@ 'use strict' -var xoLib = require('./') +process.on('unhandledRejection', function (error) { + console.log(error) +}) -var xo = new xoLib.Xo({ +var Xo = require('./').default + +var xo = new Xo({ url: 'localhost:9000' }) -xo.call('acl.get', {}).then(function (result) { - console.log('baz', result) -}).catch(function (error) { - console.log('error', error) -}) -xo.signIn({ - email: 'admin@admin.net', - password: 'admin' +xo.open().then(function () { + return xo.call('acl.get', {}).then(function (result) { + console.log('success:', result) + }).catch(function (error) { + console.log('failure:', error) + }) }).then(function () { - console.log('foo', xo.user) -}).catch(function (error) { - console.log('error', error) -}) - -xo.signIn({ - email: 'tom', - password: 'tom' + return xo.signIn({ + email: 'admin@admin.net', + password: 'admin' + }).then(function () { + console.log('connected as ', xo.user) + }).catch(function (error) { + console.log('failure:', error) + }) }).then(function () { - console.log('bar', xo.user) -}).catch(function (error) { - console.log('error', error) -}) + return xo.signIn({ + email: 'tom', + password: 'tom' + }).then(function () { + console.log('connected as', xo.user) -xo.call('acl.get', {}).then(function (result) { - console.log('plop', result) -}).catch(function (error) { - console.log('error', error) + return xo.call('acl.get', {}).then(function (result) { + console.log('success:', result) + }).catch(function (error) { + console.log('failure:', error) + }) + }).catch(function (error) { + console.log('failure', error) + }) +}).then(function () { + return xo.close() }) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 64233e9c2..03d1bef08 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -25,41 +25,62 @@ "dist/" ], "engines": { - "node": ">=0.12" + "node": ">=4" }, "dependencies": { - "jsonrpc-websocket-client": "0.1.1", - "lodash.startswith": "^4.0.0", + "jsonrpc-websocket-client": "^0.1.2", + "lodash": "^4.17.2", "make-error": "^1.0.4" }, "devDependencies": { - "babel-cli": "^6.3.17", - "babel-eslint": "^7.0.0", - "babel-preset-es2015": "^6.3.13", - "babel-preset-stage-0": "^6.3.13", - "clarify": "^2.0.0", - "dependency-check": "^2.5.1", - "mocha": "^3.0.0", - "must": "^0.13.1", - "nyc": "^8.1.0", - "source-map-support": "^0.4.0", - "standard": "^8.0.0", - "trace": "^2.0.1" + "babel-cli": "^6.18.0", + "babel-eslint": "^7.1.1", + "babel-plugin-lodash": "^3.2.9", + "babel-preset-env": "^0.0.8", + "babel-preset-stage-0": "^6.16.0", + "cross-env": "^3.1.3", + "dependency-check": "^2.6.0", + "ghooks": "^1.3.2", + "rimraf": "^2.5.4", + "standard": "^8.5.0" }, "scripts": { - "build": "NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "clean": "rimraf dist/", "depcheck": "dependency-check ./package.json", - "dev": "babel --watch --source-maps --out-dir=dist/ src/", - "dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"", + "dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/", "lint": "standard", "posttest": "npm run lint && npm run depcheck", - "prepublish": "npm run build", - "test": "nyc mocha --opts .mocha.opts \"dist/**/*.spec.js\"" + "prebuild": "npm run clean", + "predev": "npm run clean", + "prepublish": "npm run build" + }, + "babel": { + "plugins": [ + "lodash" + ], + "presets": [ + [ + "env", + { + "targets": { + "browsers": "> 5%", + "node": 4 + } + } + ], + "stage-0" + ] }, "standard": { "ignore": [ - "dist/**" + "dist" ], "parser": "babel-eslint" + }, + "config": { + "ghooks": { + "commit-msg": "npm test" + } } } diff --git a/packages/xo-lib/src/index.js b/packages/xo-lib/src/index.js index c5d3cf736..a194066bd 100644 --- a/packages/xo-lib/src/index.js +++ b/packages/xo-lib/src/index.js @@ -1,9 +1,9 @@ -import startsWith from 'lodash.startswith' import JsonRpcWebSocketClient, { OPEN, CLOSED } from 'jsonrpc-websocket-client' import { BaseError } from 'make-error' +import { startsWith } from 'lodash' // =================================================================== diff --git a/packages/xo-lib/src/index.spec.js b/packages/xo-lib/src/index.spec.js deleted file mode 100644 index 2319bd7d5..000000000 --- a/packages/xo-lib/src/index.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-env mocha */ - -import expect from 'must' - -// =================================================================== - -import myLib from './' - -// =================================================================== - -describe.skip('myLib', () => { - it('does something', () => { - // TODO: some real tests. - - expect(myLib).to.exists() - }) -}) From af1530db36a84970730387ecce5cb5a5783058ea Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 18 Nov 2016 11:51:23 +0100 Subject: [PATCH 129/134] 0.8.1 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 03d1bef08..2281d9e47 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.8.0", + "version": "0.8.1", "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ From 76cb4037d433e427cda9fa83564543c7b5d6cea6 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 18 Nov 2016 18:32:27 +0100 Subject: [PATCH 130/134] fix: build for > 2% browsers Fixes #21 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 2281d9e47..915b11507 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -64,7 +64,7 @@ "env", { "targets": { - "browsers": "> 5%", + "browsers": "> 2%", "node": 4 } } From fb32aeeeb6ad11ed4263ff5afdabf1bc404ad989 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 18 Nov 2016 18:32:35 +0100 Subject: [PATCH 131/134] 0.8.2 --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 915b11507..3ad0a01a5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -1,6 +1,6 @@ { "name": "xo-lib", - "version": "0.8.1", + "version": "0.8.2", "license": "ISC", "description": "Library to connect to XO-Server", "keywords": [ From 974650bc5697c5205a0645afd1c72ffc87df0d5c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 24 Nov 2016 22:42:22 +0100 Subject: [PATCH 132/134] chore(package): update babel-preset-env to version 0.0.9 (#22) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index 3ad0a01a5..a1ed8e410 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -36,7 +36,7 @@ "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-lodash": "^3.2.9", - "babel-preset-env": "^0.0.8", + "babel-preset-env": "^0.0.9", "babel-preset-stage-0": "^6.16.0", "cross-env": "^3.1.3", "dependency-check": "^2.6.0", From 0b92ceec901c0268229213fb25f8367f807fd33a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 10 Dec 2016 17:36:47 +0100 Subject: [PATCH 133/134] chore(package): update babel-preset-env to version 1.0.1 (#23) https://greenkeeper.io/ --- packages/xo-lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index a1ed8e410..c60c0c2e5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -36,7 +36,7 @@ "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-lodash": "^3.2.9", - "babel-preset-env": "^0.0.9", + "babel-preset-env": "^1.0.1", "babel-preset-stage-0": "^6.16.0", "cross-env": "^3.1.3", "dependency-check": "^2.6.0", From 044c9bed4c7bc196638e20c2e740359fd4bf2030 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 23 Dec 2016 11:01:25 +0100 Subject: [PATCH 134/134] Update README.md --- packages/xo-lib/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md index 44b84da4c..183f848ba 100644 --- a/packages/xo-lib/README.md +++ b/packages/xo-lib/README.md @@ -26,7 +26,7 @@ import Xo from 'xo-lib' const xo = new Xo({ url: 'https://xo.company.tld' }) // Let's start by opening the connection. -await xo.connect() +await xo.open() // Must sign in before being able to call any methods (all calls will // be buffered until signed in).