UNPKG

domotz-remote-pawn

Version:

Domotz Agent

321 lines (281 loc) 11.4 kB
/** 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;