domotz-remote-pawn
Version:
Domotz Agent
321 lines (281 loc) • 11.4 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2016 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
*
* Created by Tommaso Latini <tommaso@domotz.com> on 28/08/15.
*/
const PORTAL_API_ENDPOINT = 'https://portal.domotz.com/portal-api/v1/';
const ENDPOINT_SPECIFIER_HEADER = 'x-api-endpoint';
const DEFAULT_REQUEST_TIMEOUT = 30000;
const DEFAULT_MAX_ATTEMPTS = 0;
const DEFAULT_RETRY_DELAY = 0;
/**
* Wrapper of NODE request.
* It provides utilities like:
* - Flexible API endpoint
* - Standard callback for error and response handling
* - Wrapper for promise
* @param resourceLocator
* @param t
* @returns {{
* setCredentials: setCredentials,
* send: send,
* sendWithPromise: sendWithPromise,
* getBaseURL: Function
* }}
*/
var factory = function (resourceLocator, t) {
var request = resourceLocator.request;
var q = resourceLocator.q;
var conf = resourceLocator.configuration;
var stringify = resourceLocator.stringify;
var httpOutCache = resourceLocator.httpOutCache;
var myConsole = resourceLocator.log.decorateLogs();
/**
* PORTAL_API_ENDPOINT should be an environment variable
* only in development mode
*/
var type = t;
var baseUrl = process.env.PORTAL_API_ENDPOINT || PORTAL_API_ENDPOINT;
var credentials = null;
var xSessionToken = null;
function setCredentials(user, password) {
credentials = { user: user, password: password };
}
function getUrl(doNotAddBaseUrl, resource) {
return doNotAddBaseUrl ? resource : baseUrl + resource;
}
function getMethod(method) {
var METHOD_TO_REQUEST_MAP = {
POST: 'post',
post: 'post',
GET: 'get',
get: 'get',
PUT: 'put',
put: 'put',
DELETE: 'del',
delete: 'del',
HEAD: 'head',
head: 'head',
PATCH: 'patch',
patch: 'patch',
};
return METHOD_TO_REQUEST_MAP[method];
}
function getBaseURL() {
return baseUrl;
}
function getOptions(url, options) {
options = options || {};
options.url = url;
if (!options.headers) {
options.headers = {};
}
if (xSessionToken) {
options.headers['x-session-token'] = xSessionToken;
}
options.auth = credentials;
options.gzip = true; // Enable gzip/deflate compression
options.timeout = options.timeout || DEFAULT_REQUEST_TIMEOUT;
options.maxAttempts = options.maxAttempts || DEFAULT_MAX_ATTEMPTS;
options.retryDelay = options.retryDelay || DEFAULT_RETRY_DELAY;
return options;
}
function logRequest(options, verb, url, body) {
var stringBody = stringify(body) || '';
myConsole.info(
'%s: %s request to %s. Body: %d bytes%s',
type,
verb,
url,
stringBody.length,
options ? '. Options: ' + stringify(options) : ''
);
myConsole.debug('%s: Request Body: %s', type, stringBody);
}
function _doNotAddBaseUrl(doNotAddBaseUrl) {
return doNotAddBaseUrl ? 'custom' : type;
}
function responseHandler(url, verb, response, body, customHandler, doNotAddBaseUrl) {
if (response.attempts) {
myConsole.debug('Response received after %s attempts.', response.attempts);
}
myConsole.debug(
'(%s): Engine request OK! Requested URL: <%s> ' + 'Method: %s - Status Code: %d. Server encoded the data as: %s. %s',
_doNotAddBaseUrl(doNotAddBaseUrl),
url,
verb,
response.statusCode,
response.headers['content-encoding'] || 'identity',
body ? 'Payload size ' + body.length : ''
);
myConsole.verbose('Response Body: %s', body || '');
if (customHandler !== undefined) {
customHandler(response, body);
}
}
function errorHandler(url, verb, error, customHandler, doNotAddBaseUrl) {
myConsole.warn(
'(%s): Engine request error! Requested URL: <%s> Method: %s - ' + 'Error code: %s. %s => %s',
_doNotAddBaseUrl(doNotAddBaseUrl),
url,
verb,
error.status || 'No HTTP error',
error.status ? 'Response Body' : 'Error message',
stringify(error.message)
);
myConsole.warn(error.stack);
if (customHandler !== undefined) {
customHandler(error);
}
}
function doRequest(options, onSuccess, onError, doNotAddBaseUrl) {
request[options.method](options, function callback(error, response, body) {
try {
if (error) {
errorHandler(options.url, options.method, error, onError, doNotAddBaseUrl);
return;
}
var statusCode = response.statusCode;
if ((statusCode > 299 || statusCode < 200) && statusCode !== 304) {
error = {
message: body,
status: statusCode,
};
errorHandler(options.url, options.method, error, onError, doNotAddBaseUrl);
return;
}
var location = response.headers.location;
if (statusCode === 204 && location && options.doNotFollow204 !== true) {
send(location, 'GET', null, onSuccess, onError, true);
return;
}
var xApiEndpointHeader = response.headers[ENDPOINT_SPECIFIER_HEADER];
if (xApiEndpointHeader) {
xApiEndpointHeader = xApiEndpointHeader.split(',')[0];
myConsole.info('(%s): Switching baseURL to: %s', type, xApiEndpointHeader);
baseUrl = xApiEndpointHeader.split(', ')[0];
}
responseHandler(options.url, options.method, response, body, onSuccess, doNotAddBaseUrl);
} catch (exception) {
errorHandler(options.url, options.method, exception, onError);
}
});
}
function send(resource, verb, body, onSuccess, onError, doNotAddBaseUrl, otherOptions) {
var url = getUrl(doNotAddBaseUrl, resource);
var options = getOptions(url, otherOptions);
options.method = getMethod(verb);
if (body !== null && body !== undefined) {
options.headers['content-type'] = 'application/json';
if (options.toSendCompressed) {
options.headers['content-encoding'] = 'gzip';
resourceLocator.zlib.gzip(JSON.stringify(body), function (error, result) {
if (error) {
var errorStr = 'Unable to compress the body: ' + error.message;
myConsole.error(errorStr);
throw new Error(errorStr);
}
options.body = result;
logRequest(otherOptions, verb, options.url, body);
doRequest(options, onSuccess, onError, doNotAddBaseUrl);
});
} else {
options.body = stringify(body);
logRequest(otherOptions, verb, options.url, body);
doRequest(options, onSuccess, onError, doNotAddBaseUrl);
}
} else {
logRequest(otherOptions, verb, options.url, body);
doRequest(options, onSuccess, onError, doNotAddBaseUrl);
}
}
function sendWithPromise(resource, method, body, handleResponse, doNotAddBaseUrl, otherOptions) {
var deferred = q.defer();
function parseSuccessfulResponse(response, body) {
if (typeof handleResponse === 'function') {
deferred.resolve(handleResponse(response));
} else if (typeof body === 'string' && body !== '') {
deferred.resolve(JSON.parse(body));
} else {
deferred.resolve(body);
}
}
function parseError(error) {
deferred.reject(error);
}
send(resource, method, body, parseSuccessfulResponse, parseError, doNotAddBaseUrl, otherOptions);
return deferred.promise;
}
function cachedSendWithPromise(resource, method, body, handleResponse, doNotAddBaseUrl, otherOptions) {
if (['put', 'post'].indexOf(method.toLowerCase()) !== -1 && body) {
if (httpOutCache.checkCallHash(resource, method, body)) {
return q.resolve();
}
}
var promise = sendWithPromise(resource, method, body, handleResponse, doNotAddBaseUrl, otherOptions);
promise.then(function () {
var expiration = undefined;
if (otherOptions && otherOptions.cache_until) {
expiration = otherOptions.cache_until;
}
if (body) {
httpOutCache.storeCallHash(resource, method, body, expiration);
}
});
return promise;
}
switch (type) {
case 'register':
myConsole.verbose('Register API initialization');
break;
case 'agent':
myConsole.verbose('Agent API initialization');
baseUrl = conf.engine.api_endpoints.hub + 'agent/' + conf.id;
credentials = conf.credentials;
break;
case 'agent-heartbeat':
myConsole.verbose('Agent-Heartbeat API initialization');
baseUrl = conf.engine.api_endpoints.hub + 'agent/' + conf.credentials.user;
credentials = conf.credentials;
break;
case 'user':
myConsole.verbose('User API initialization');
baseUrl = conf ? conf.engine.api_endpoints.app : baseUrl;
break;
case 'other':
break; // Neither baseUrl nor credentials
default:
throw new Error('Wrong type for engine client');
}
function cleanData() {
baseUrl = process.env.PORTAL_API_ENDPOINT || PORTAL_API_ENDPOINT;
xSessionToken = null;
}
function setSessionToken(sessionToken) {
xSessionToken = sessionToken;
}
myConsole.info('(%s): Create api client. Endpoint: %s.', type, baseUrl);
return {
getBaseURL: getBaseURL,
setCredentials: setCredentials,
send: send,
sendWithPromise: sendWithPromise,
cachedSendWithPromise: cachedSendWithPromise,
cleanData: cleanData,
setSessionToken: setSessionToken,
};
};
module.exports.factory = factory;