diff --git a/packages/xo-lib/.editorconfig b/packages/xo-lib/.editorconfig new file mode 100644 index 000000000..da21ef4c5 --- /dev/null +++ b/packages/xo-lib/.editorconfig @@ -0,0 +1,65 @@ +# http://EditorConfig.org +# +# Julien Fontanet's configuration +# https://gist.github.com/julien-f/8096213 + +# Top-most EditorConfig file. +root = true + +# Common config. +[*] +charset = utf-8 +end_of_line = lf +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 new file mode 100644 index 000000000..827e4e420 --- /dev/null +++ b/packages/xo-lib/.gitignore @@ -0,0 +1,7 @@ +/dist/ +/node_modules/ + +npm-debug.log +npm-debug.log.* +pnpm-debug.log +pnpm-debug.log.* diff --git a/packages/xo-lib/.npmignore b/packages/xo-lib/.npmignore new file mode 100644 index 000000000..c31ee82cb --- /dev/null +++ b/packages/xo-lib/.npmignore @@ -0,0 +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 new file mode 100644 index 000000000..1a582c426 --- /dev/null +++ b/packages/xo-lib/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - stable + - 6 + - 4 + +# Use containers. +# http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false diff --git a/packages/xo-lib/README.md b/packages/xo-lib/README.md new file mode 100644 index 000000000..183f848ba --- /dev/null +++ b/packages/xo-lib/README.md @@ -0,0 +1,168 @@ +# 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. + +## Install + +Installation of the [npm package](https://npmjs.org/package/xo-lib): + +``` +npm install --save xo-lib +``` + +Then require the package: + +```javascript +import Xo from 'xo-lib' +``` + +## Usage + +> 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. +const xo = new Xo({ url: 'https://xo.company.tld' }) + +// Let's start by opening the connection. +await xo.open() + +// Must sign in before being able to call any methods (all calls will +// be buffered until signed in). +await xo.signIn({ + email: 'admin@admin.net', + password: 'admin' +}) + +console('signed as', xo.user) +``` + +The credentials can also be passed directly to the constructor: + +```javascript +const xo = Xo({ + url: 'https://xo.company.tld', + credentials: { + email: 'admin@admin.net', + password: 'admin', + } +}) + +xo.open() + +xo.on('authenticated', () => { + console.log(xo.user) +}) +``` + +> 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 +await xo.open() + +console.log('connected') +``` + +### Disconnection + +```javascript +xo.close() + +console.log('disconnected') +``` + +### Method call + +```javascript +const token = await xo.call('token.create') + +console.log('Token created', token) +``` + +### 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 +xo.on('open', () => { + console.log('connected') +}) +``` + +```javascript +xo.on('closed', () => { + console.log('disconnected') +}) +``` + +```javascript +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 + +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/example.js b/packages/xo-lib/example.js new file mode 100644 index 000000000..77c83ec1a --- /dev/null +++ b/packages/xo-lib/example.js @@ -0,0 +1,45 @@ +'use strict' + +process.on('unhandledRejection', function (error) { + console.log(error) +}) + +var Xo = require('./').default + +var xo = new Xo({ + url: 'localhost:9000' +}) + +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 () { + 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 () { + return xo.signIn({ + email: 'tom', + password: 'tom' + }).then(function () { + console.log('connected as', xo.user) + + 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 new file mode 100644 index 000000000..c60c0c2e5 --- /dev/null +++ b/packages/xo-lib/package.json @@ -0,0 +1,86 @@ +{ + "name": "xo-lib", + "version": "0.8.2", + "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" + }, + "author": { + "name": "Julien Fontanet", + "email": "julien.fontanet@vates.fr" + }, + "preferGlobal": false, + "main": "dist/", + "bin": {}, + "files": [ + "dist/" + ], + "engines": { + "node": ">=4" + }, + "dependencies": { + "jsonrpc-websocket-client": "^0.1.2", + "lodash": "^4.17.2", + "make-error": "^1.0.4" + }, + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-eslint": "^7.1.1", + "babel-plugin-lodash": "^3.2.9", + "babel-preset-env": "^1.0.1", + "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": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/", + "clean": "rimraf dist/", + "depcheck": "dependency-check ./package.json", + "dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/", + "lint": "standard", + "posttest": "npm run lint && npm run depcheck", + "prebuild": "npm run clean", + "predev": "npm run clean", + "prepublish": "npm run build" + }, + "babel": { + "plugins": [ + "lodash" + ], + "presets": [ + [ + "env", + { + "targets": { + "browsers": "> 2%", + "node": 4 + } + } + ], + "stage-0" + ] + }, + "standard": { + "ignore": [ + "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 new file mode 100644 index 000000000..a194066bd --- /dev/null +++ b/packages/xo-lib/src/index.js @@ -0,0 +1,83 @@ +import JsonRpcWebSocketClient, { + OPEN, + CLOSED +} from 'jsonrpc-websocket-client' +import { BaseError } from 'make-error' +import { startsWith } from 'lodash' + +// =================================================================== + +const noop = () => {} + +// =================================================================== + +export class XoError extends BaseError {} + +// ------------------------------------------------------------------- + +export default class Xo extends JsonRpcWebSocketClient { + constructor (opts) { + const url = opts && opts.url || '.' + super(`${url === '/' ? '' : 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, i) { + if (startsWith(method, 'session.')) { + return Promise.reject( + new XoError('session.*() methods are disabled from this interface') + ) + } + + 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 + } + + refreshUser () { + return super.call('session.getUser').then(user => { + return (this._user = user) + }) + } + + 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') + }, + error => { + this.emit('authenticationFailure', error) + throw error + } + ) + } +}