import * as url from 'url'; import * as Q from 'q'; import * as Promise from 'bluebird'; import * as utils from '@cobalt-engine/utils'; import * as requestPromise from 'request-promise'; import * as request from 'request'; import got, { PlainResponse } from 'got'; import CustomError from '../custom-error'; import { CoError } from '../co-error'; import createOutputStream = require('create-output-stream'); import { Config } from '../config'; import IRequest from '../interfaces/IRequest'; export default class Request implements IRequest { constructor() {} public get(url: string, data?: any): Promise { return sendRequest('GET', url, data); } public post(url: string, data?: any): Promise { return sendRequest('POST', url, data); } public put(url: string, data?: any): Promise { return sendRequest('PUT', url, data); } public delete(url: string, data?: any): Promise { return sendRequest('DELETE', url, data); } public patch(url: string, data?: any): Promise { return sendRequest('PATCH', url, data); } public download(url: string, filename: string, data?: any): Promise { return Promise.resolve(this.downloadProcces(url, filename, data)) .then(() => {}) .catch(err => errorHandler(err, url)); } private downloadProcces(url: string, filename: string, data?: any): Q.Promise { const deferred = Q.defer(); const downloadStream = got.stream(url, getGotOptions(data)); const outputStream = createOutputStream(filename); downloadStream .on('response', (response: PlainResponse) => { if (response.statusCode !== 200) { const statusCode = response.statusCode; if (statusCode) { deferred.reject(new CoError(statusCode)); } else { deferred.reject(new CoError('Server error')); } } }) .on('error', deferred.reject); outputStream.once('close', () => { deferred.resolve(); }); outputStream.on('error', deferred.reject); downloadStream.pipe(outputStream); return deferred.promise; } } function getGotOptions(data: any = {}) { return { headers: { ...data.headers, authorization: `bearer ${data.auth.bearer}`, }, }; } function sendRequest(method: string, url: string, data: any = {}): Promise { return Config.get() .then(config => config.authServer.realm) .then(tenant => Object.assign(data, { method, uri: url, transform: parseResponse, headers: { tenant } })) .then(options => requestPromise(options)) .catch(err => errorHandler(err.cause, url)); } function parseResponse(body: any, res: request.RequestResponse): any { const data = utils.jsonOrText(body); if (res.statusCode === 200) { return data; } if (data.error && data.error_description) { throw new CustomError(data.error.toUpperCase(), data.error_description); } if (data.status && data.message) { throw new CustomError(data.status, data.message); } if (res.statusCode) { throw new CoError(res.statusCode); } } function errorHandler(err: CustomError, url: string): void { if (isInvalidUrlError(err)) { throw new CoError('INVALID_URL', url); } else if (isServerError(err)) { throw new CoError('SERVER_UNAVAILABLE', extractHost(url)); } else if (isNotConnect(err)) { throw new CoError('ECONNREFUSED', extractHost(url)); } throw err; } function extractHost(fullUrl: string): string { const urlObj = url.parse(fullUrl); return url.format({ protocol: urlObj.protocol, host: urlObj.host, }); } function isInvalidUrlError(err: CustomError): boolean { return err.message && err.message.indexOf('Invalid URI') > -1 || err.code === 'ENOTFOUND'; } function isServerError(err: CustomError): boolean { return err.code === 'SERVER_ERROR'; } function isNotConnect(err: CustomError) { return err.code == 'ECONNREFUSED'; }