domotz-remote-pawn
Version:
Domotz Agent
226 lines (193 loc) • 7.97 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 MODULE_NAME = 'API_CLIENT';
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 q
* @param request
* @param conf
* @param t
* @returns {{
* setCredentials: setCredentials,
* send: send,
* sendWithPromise: sendWithPromise,
* getBaseURL: Function
* }}
*/
var factory = function (q, request, conf, t) {
/**
* 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;
function setCredentials(user, password) {
credentials = {user: user, password: password};
}
function getUrl(doNotAddBaseUrl, resource) {
return doNotAddBaseUrl ? resource : baseUrl + resource;
}
function getMethod(method) {
return request.METHOD_TO_REQUEST_MAP[method];
}
function getBaseURL() {
return baseUrl;
}
function getOptions(url, body, options) {
options = options || {};
options.url = url;
options.json = body;
options.auth = credentials;
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 = JSON.stringify(body) || '';
console.info('%s - (%s): %s request to %s endpoint: %s. Body: %d bytes%s',
MODULE_NAME, type, verb, type, url, stringBody.length,
options ? '. Options: ' + JSON.stringify(options, hidePassword) : '');
console.debug('%s - (%s): Request Body: %s', MODULE_NAME, type, stringBody);
}
function responseHandler(url, verb, response, body, customHandler, doNotAddBaseUrl) {
if (response.attempts && response.attempts > 1) {
console.debug("Response received after " + response.attempts + " attempts.");
}
console.debug('%s - (%s): Engine request OK! Requested URL: <%s> ' +
'Method: %s - Status Code: %d', MODULE_NAME,
(doNotAddBaseUrl ? 'custom' : type), url, verb, response.statusCode);
console.verbose("%s - Response Body: %s", MODULE_NAME, body);
if (customHandler !== undefined) {
customHandler(response, body);
}
}
function errorHandler(url, verb, error, customHandler, doNotAddBaseUrl) {
console.warn('%s - (%s): Engine request error! Requested URL: <%s> Method: %s - ' +
'Error code: %s. %s => %s', MODULE_NAME, (doNotAddBaseUrl ? 'custom' : type),
url, verb, (error.status || 'No HTTP error'), error.status ? "Response Body" :
"Error message", JSON.stringify(error.message));
error.stack = error.stack || new Error(error).stack;
console.warn(error.stack);
if (customHandler !== undefined) {
customHandler(error);
}
}
function send(resource, verb, body, onSuccess, onError, doNotAddBaseUrl, otherOptions) {
var url = getUrl(doNotAddBaseUrl, resource);
var method = getMethod(verb);
var options = getOptions(url, body, otherOptions);
logRequest(otherOptions, verb, url, body);
request[method](options, function callback(error, response, body) {
try {
if (error) {
errorHandler(url, verb, error, onError, doNotAddBaseUrl);
return;
}
var statusCode = response.statusCode;
if (statusCode > 299 || statusCode < 200) {
error = {
message: body,
status: statusCode
};
errorHandler(url, verb, error, onError, doNotAddBaseUrl);
return;
}
var location = response.headers.location;
if (statusCode === 204 && location) {
send(location, 'GET', null, onSuccess, onError, true);
return;
}
var xApiEndpointHeader = response.headers[ENDPOINT_SPECIFIER_HEADER];
if (xApiEndpointHeader) {
console.info("%s - (%s): Switching baseURL to: %s", MODULE_NAME, type, xApiEndpointHeader);
baseUrl = xApiEndpointHeader;
}
responseHandler(url, verb, response, body, onSuccess, doNotAddBaseUrl);
} catch (exception) {
errorHandler(url, verb, exception, onError);
}
});
}
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') {
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;
}
switch (type) {
case 'register':
console.verbose('Register API initialization');
break;
case 'agent':
console.verbose('Agent API initialization');
baseUrl = conf.engine.api_endpoints.hub + 'agent/' + conf.id;
credentials = conf.credentials;
break;
case 'agent-heartbeat':
console.verbose('Agent-Heartbeat API initialization');
baseUrl = conf.engine.api_endpoints.hub + 'agent/' + conf.credentials.user;
credentials = conf.credentials;
break;
case 'user':
console.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");
}
console.info('%s - (%s): Create api client. Endpoint: %s.', MODULE_NAME, type, baseUrl);
return {
getBaseURL: getBaseURL,
setCredentials: setCredentials,
send: send,
sendWithPromise: sendWithPromise
};
};
module.exports.factory = factory;
function hidePassword(key, value) {
if (key === 'password' || key === 'pass') {
return '*********';
} else {
return value;
}
}