node-hue-api
Version:
Philips Hue API Library for Node.js
156 lines (155 loc) • 5.1 kB
JavaScript
import fetch from 'node-fetch';
import { URLSearchParams } from 'url';
import HttpError from './HttpError';
export class HttpClientFetch {
constructor(config) {
this._config = config || {};
}
get headers() {
return this._config.headers || {};
}
get baseURL() {
return this._config.baseURL;
}
get hasBaseUrl() {
return !!this.baseURL;
}
getTimeout(timeout) {
var _a;
if (timeout !== undefined) {
return timeout;
}
return ((_a = this._config) === null || _a === void 0 ? void 0 : _a.timeout) || 0;
}
refreshAuthorizationHeader(token) {
if (!this._config.headers) {
this._config.headers = {};
}
this._config.headers['Authorization'] = `Bearer ${token}`;
}
getAgent(url, config) {
const specifiedAgent = config.agent || config.httpsAgent || config.httpAgent || undefined;
if (specifiedAgent) {
return specifiedAgent;
}
return this._config.httpsAgent || this._config.httpAgent || undefined;
}
getUrl(url) {
if (!this.hasBaseUrl) {
return url;
}
else if (/^http/.test(url)) {
return url;
}
let path;
if (url && url[0] === '/') {
path = url;
}
else {
path = `/${url}`;
}
return `${this.baseURL}${path}`;
}
request(req) {
const isJson = req.json === true, hasData = !!req.data, url = this.getUrl(req.url), headers = this.headers, config = {
method: req.method,
headers: headers,
timeout: this.getTimeout(req.timeout),
};
// We are setting the timeout on the HTTP(s) agent, but node-fetch does not appear to be respecting this setting
// from the agent, so taking to explicitly extracting the timeout from the agent and setting it on the API call
// if a timeout is not specified as part of the request.
if (isJson) {
headers['Content-Type'] = 'application/json';
headers['Accept'] = 'application/json';
if (hasData) {
config.body = JSON.stringify(req.data);
}
}
else {
if (hasData) {
config.body = req.data;
}
}
if (req.headers) {
const requestHeaders = req.headers;
Object.keys(requestHeaders).forEach(header => {
headers[header] = requestHeaders[header];
});
}
if (req.params) {
config.body = new URLSearchParams(req.params);
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
config.agent = this.getAgent(url, req);
return fetch(url, config)
.then((res) => {
if (req.validateStatus) {
if (req.validateStatus(res.status)) {
return res;
}
}
else if (res.ok) {
return res;
}
// Process the result and then return the error object
return resolveBodyPromise(res)
.then(data => {
throw new HttpError(res.status, res.url, res.headers.raw(), data);
});
})
.then((res) => {
const result = {
status: res.status,
};
if (res.headers) {
// @ts-ignore
result.headers = res.headers.raw();
}
return resolveBodyPromise(res)
.then(data => {
result.data = data;
return result;
});
});
}
}
function resolveBodyPromise(res) {
// The local bridge connection with nginx in front of it does not return a content-length header, unlike the remote API
// so we cannot gate on this and prevent calls to res.json() from errorring on an empty string.
//
// This means we need to get it back as text and process it accordingly.
// let promise;
// const contentLength: string = res.headers.get('content-length');
// if (contentLength && parseInt(contentLength) > 0) {
// const contentType = res.headers.get('content-type');
//
// if (contentType.startsWith('application/json')) {
// promise = res.json();
// } else {
// promise = res.text();
// }
// } else {
// promise = Promise.resolve();
// }
// return promise;
return res.text()
.then((data) => {
const contentType = res.headers.get('content-type');
if (contentType && contentType.startsWith('application/json')) {
try {
return JSON.parse(data);
}
catch (err) {
return data;
}
}
return data;
});
}
export function create(config) {
return new HttpClientFetch(config);
}
export function request(req) {
return new HttpClientFetch().request(req);
}