const extend = require('extend');
const qs = require('querystring');
const Base = require('./base');
const ServerVersion = require('./server-version');
const Operation = require('./operation');
const Request = require('./request');
const Repository = require('./repository');
const BatchUpload = require('./upload/batch');
const Users = require('./user/users');
const Groups = require('./group/groups');
const Directory = require('./directory/directory');
const Workflows = require('./workflow/workflows');
const join = require('./deps/utils/join');
const Promise = require('./deps/promise');
const FormData = require('./deps/form-data');
const Authentication = require('./auth/auth');
const Unmarshallers = require('./unmarshallers/unmarshallers');
const doFetch = require('./deps/fetch');
const API_PATH_V1 = 'api/v1/';
const AUTOMATION = 'automation/';
const DEFAULT_OPTS = {
baseURL: 'http://localhost:8080/nuxeo/',
apiPath: API_PATH_V1,
promiseLibrary: null,
auth: null,
};
/**
* The `Nuxeo` class allows using the REST API of a Nuxeo Platform instance.
* @extends Base
*
* @example
* var Nuxeo = require('nuxeo')
* var nuxeo = new Nuxeo({
* baseURL: 'http://localhost:8080/nuxeo',
* auth: {
* method: 'basic',
* username: 'Administrator',
* password: 'Administrator'
* }
* });
* nuxeo.request('path/')
* .get()
* .then(function(doc) {
* // doc.uid !== null
* });
*/
class Nuxeo extends Base {
/**
* Creates a new Nuxeo instance.
* @param {object} [opts] - The configuration options.
* @param {string} [opts.baseURL=http://localhost:8080/nuxeo/] - Base URL of the Nuxeo Platform.
* @param {string} [opts.apiPath=api/v1] - The API path.
* @param {object} [opts.auth] - The authentication configuration.
*/
constructor(opts = {}) {
const options = extend(true, {}, DEFAULT_OPTS, opts);
super(options);
this._baseURL = options.baseURL;
this._restURL = join(this._baseURL, options.apiPath);
this._automationURL = join(this._restURL, AUTOMATION);
this._auth = options.auth;
this._authenticationRefreshedListeners = [];
this.connected = false;
this.Promise = Nuxeo.Promise || Promise;
this._activeRequests = 0;
}
/**
* Connects to the Nuxeo Platform instance using the configured authentication.
*
* This method fills the `user` property with the current user
* and the `serverVersion` property with the Nuxeo Server version.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Promise} A promise resolved with the connected client.
*/
connect(opts = {}) {
let finalOptions = {
method: 'GET',
url: join(this._baseURL, 'json/cmis'),
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return this.http(finalOptions)
.then((res) => {
if (res && res.default && res.default.productVersion) {
this.serverVersion = new ServerVersion(res.default.productVersion);
this.nuxeoVersion = res.default.productVersion;
}
// log the user
finalOptions.method = 'POST';
finalOptions.url = join(this._automationURL, 'login');
return this.http(finalOptions);
})
.then((res) => this.users({ enrichers: { user: ['userprofile'] } }).fetch(res.username))
.then((user) => {
this.user = user;
this.connected = true;
return this;
});
}
/**
* Connects to the Nuxeo Platform instance using the configured authentication.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Promise} A promise resolved with the logged in user.
* @deprecated since version 3.0, use {#connect} instead.
*/
login(opts = {}) {
return this.connect(opts);
}
/**
* Does a http request.
*
* To be used when doing any call on Nuxeo Platform.
*/
http(opts = {}) {
const options = this._computeFetchOptions(opts);
return new this.Promise((resolve, reject) => {
this._activeRequests += 1;
const fetchOptions = {
method: options.method,
headers: options.headers,
body: options.body,
signal: options.signal,
};
if (opts.credentials) {
fetchOptions.credentials = opts.credentials;
} else if (!this._auth) {
fetchOptions.credentials = 'include';
}
doFetch(options.url, fetchOptions)
.then((res) => {
this._activeRequests -= 1;
if (res.status === 401 && !opts.refreshedAuthentication
&& Authentication.canRefreshAuthentication(this._auth)) {
// try re-authenticate
opts.refreshedAuthentication = true;
return Authentication.refreshAuthentication(this._baseURL, this._auth)
.then((refreshedAuth) => {
this._auth = refreshedAuth;
this._notifyAuthenticationRefreshed(refreshedAuth);
return resolve(this.http(opts));
})
.catch(() => {
const error = new Error(res.statusText);
error.response = res;
return reject(error);
});
}
if (!(/^2/.test(`${res.status}`))) {
const error = new Error(res.statusText);
error.response = res;
return reject(error);
}
if (options.resolveWithFullResponse || res.status === 204) {
return resolve(res);
}
const contentType = res.headers.get('content-type');
if (contentType && contentType.indexOf('application/json') === 0) {
options.nuxeo = this;
return resolve(res.json().then((json) => Unmarshallers.unmarshall(json, options, res)));
}
return resolve(res);
}).catch((error) => {
this._activeRequests -= 1;
return reject(error);
});
});
}
/**
* Does a http request.
*
* To be used when doing any call on Nuxeo Platform.
* @deprecated since version 3.3, use {#http} instead.
*/
_http(opts = {}) {
return this.http(opts);
}
_computeFetchOptions(opts) {
let options = {
method: 'GET',
headers: {},
json: true,
cache: false,
resolveWithFullResponse: false,
};
options = extend(true, {}, options, opts);
const authenticationHeaders = Authentication.computeAuthenticationHeaders(this._auth);
options.headers = extend(options.headers, authenticationHeaders);
if (options.schemas && options.schemas.length > 0) {
options.headers.properties = options.schemas.join(',');
}
if (opts.repositoryName !== undefined) {
options.headers['X-NXRepository'] = options.repositoryName;
}
if (opts.enrichers) {
Object.keys(opts.enrichers).forEach((key) => {
options.headers[`enrichers-${key}`] = options.enrichers[key].join(',');
});
}
if (opts.fetchProperties) {
Object.keys(opts.fetchProperties).forEach((key) => {
options.headers[`fetch-${key}`] = options.fetchProperties[key].join(',');
});
}
if (opts.translateProperties) {
Object.keys(opts.translateProperties).forEach((key) => {
options.headers[`translate-${key}`] = options.translateProperties[key].join(',');
});
}
if (options.depth) {
options.headers.depth = options.depth;
}
const { httpTimeout, transactionTimeout } = this._computeTimeouts(options);
if (transactionTimeout) {
options.headers['Nuxeo-Transaction-Timeout'] = transactionTimeout;
}
options.timeout = httpTimeout;
if (options.json) {
options.headers.Accept = 'application/json';
options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json';
// do not stringify FormData
if (typeof options.body === 'object' && !(options.body instanceof FormData)) {
options.body = JSON.stringify(options.body);
}
}
if (options.method === 'GET') {
delete options.headers['Content-Type'];
}
if (options.queryParams && Object.keys(options.queryParams).length > 0) {
options.url += options.url.indexOf('?') === -1 ? '?' : '';
options.url += qs.stringify(options.queryParams);
}
return options;
}
_computeTimeouts(options) {
const transactionTimeout = options.transactionTimeout || options.timeout;
let { httpTimeout } = options;
if (!httpTimeout && transactionTimeout) {
// make the http timeout a bit longer than the transaction timeout
httpTimeout = 5 + transactionTimeout;
}
return { httpTimeout, transactionTimeout };
}
/**
* Creates a new {@link Operation} object.
* @param {string} id - The operation ID.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Operation}
*/
operation(id, opts = {}) {
let finalOptions = {
id,
nuxeo: this,
url: this._automationURL,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Operation(finalOptions);
}
/**
* Creates a new {@link Request} object.
* @param {string} path - The request default path.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Request}
*/
request(path, opts = {}) {
let finalOptions = {
path,
nuxeo: this,
url: this._restURL,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Request(finalOptions);
}
/**
* Creates a new {@link Repository} object.
* @param {string} name - The repository name. Default to the Nuxeo's repository name.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Repository}
*/
repository(name = null, opts = {}) {
let repositoryName = name;
let options = opts;
if (typeof repositoryName === 'object') {
options = repositoryName;
repositoryName = null;
}
let finalOptions = {
nuxeo: this,
};
if (repositoryName) {
finalOptions.repositoryName = repositoryName;
}
finalOptions = extend(true, finalOptions, options);
finalOptions = this._computeOptions(finalOptions);
return new Repository(finalOptions);
}
/**
* Creates a new {@link BatchUpload} object.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {BatchUpload}
*/
batchUpload(opts = {}) {
let finalOptions = {
nuxeo: this,
url: this._restURL,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new BatchUpload(finalOptions);
}
/**
* Creates a new {@link Users} object to manage users.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Users}
*/
users(opts = {}) {
let finalOptions = {
nuxeo: this,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Users(finalOptions);
}
/**
* Creates a new {@link Groups} object to manage groups.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Groups}
*/
groups(opts = {}) {
let finalOptions = {
nuxeo: this,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Groups(finalOptions);
}
/**
* Creates a new {@link Directory} object.
* @param {string} name - The directory name.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Directory}
*/
directory(name, opts = {}) {
let finalOptions = {
directoryName: name,
nuxeo: this,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Directory(finalOptions);
}
/**
* Creates a new {@link Workflows} object.
* @param {string} name - The repository name. Default to the Nuxeo's repository name.
* @param {object} [opts] - Options overriding the ones from this object.
* @returns {Workflows}
*/
workflows(repositoryName = this._repositoryName, opts = {}) {
let finalOptions = {
repositoryName,
nuxeo: this,
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return new Workflows(finalOptions);
}
requestAuthenticationToken(applicationName, deviceId, deviceDescription, permission, opts = {}) {
let finalOptions = {
method: 'GET',
url: join(this._baseURL, 'authentication', 'token'),
queryParams: {
applicationName,
deviceId,
deviceDescription,
permission,
},
};
finalOptions = extend(true, finalOptions, opts);
finalOptions = this._computeOptions(finalOptions);
return this.http(finalOptions)
.then((res) => res.text());
}
computeAuthenticationHeaders() {
return Authentication.computeAuthenticationHeaders(this._auth);
}
authenticateURL(url) {
return Authentication.authenticateURL(url, this._auth);
}
onAuthenticationRefreshed(listener) {
this._authenticationRefreshedListeners.push(listener);
}
_notifyAuthenticationRefreshed(refreshedAuthentication) {
this._authenticationRefreshedListeners.forEach((listener) => listener.call(this, refreshedAuthentication));
}
}
/**
* Sets the Promise library class to use.
*/
Nuxeo.promiseLibrary = (promiseLibrary) => {
Nuxeo.Promise = promiseLibrary;
};
/**
* Registers an Authenticator for a given authentication method.
*/
Nuxeo.registerAuthenticator = (method, authenticator) => {
Authentication.registerAuthenticator(method, authenticator);
};
/**
* Registers an Unmarshaller for a given entity type.
*/
Nuxeo.registerUnmarshaller = (entityType, unmarshaller) => {
Unmarshallers.registerUnmarshaller(entityType, unmarshaller);
};
module.exports = Nuxeo;