Transport.js
3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
'use strict';
const Bottleneck = require('bottleneck')
, ApiError = require('../../ApiError')
, HueError = require('../../HueError')
, util = require('../../util')
;
// The limiter configuration if nothing is specified
const DEFAULT_LIMITER_CONFIG = {
maxConcurrent: 4,
minTime: 50,
};
module.exports = class Transport {
constructor(username, client, queueConfig) {
this._username = username;
this._client = client;
this.configureLimiter(queueConfig || DEFAULT_LIMITER_CONFIG);
}
configureLimiter(config) {
this._limiter = new Bottleneck(config);
}
get limiter() {
return this._limiter;
}
/**
* Executes an API Endpoint Request.
* @param {ApiEndpoint} api The Api endpoint to perform a request against.
* @param {Object=} parameters Any parameters specific to the request.
* @returns {Promise<any>} The promise for making the specified API request.
*/
execute(api, parameters) {
let self = this
, limiter = this.limiter
, client = self._client
, requestParameters = Object.assign({username: self._username}, parameters)
, promise
;
if (!api) {
throw new Error('An API must be provided');
}
promise = limiter.schedule(() => {return client.request(api.getRequest(requestParameters))})
.catch(err => {
throw extractError(err, err.response);
})
.then(res => {
// Errors can be contained in the object payload from a successful response code.
const errors = util.parseErrors(res.data);
if (errors) {
throw new ApiError(errors[0]);
}
return res.data;
});
if (api.getErrorHandler()) {
promise = promise.catch(api.getErrorHandler());
}
if (api.getPostProcessing()) {
// Inject the request parameters into the post processing function
promise = promise.then(result => {
return api.getPostProcessing()(result, requestParameters);
});
}
return promise;
}
refreshAuthorizationHeader(token) {
// Update the default common authorization header with the new bearer token
this._client.refreshAuthorizationHeader(`Bearer ${token}`);
}
};
/**
* Extracts an appropriate error from the provided details.
*
* @param {Error} err The captured Error.
* @param {Object} response The underlying transport HTTP response object.
* @returns {ApiError | HueError} The error extracted from the data provided
*/
function extractError(err, response) {
if (!response) {
//TODO fetch leaks the API key in the URL, need to redact it
const error = new ApiError(err.message);
// Set the original stack trace here as the one for the error created is pretty much useless and obscures the real problem
error.stack = err.stack
throw error;
}
const headers = response.headers
, authenticateHeader = headers ? headers['www-authenticate'] : null
;
let hueError;
if (authenticateHeader) {
const errorMatch = /error="(.*?)"/.exec(authenticateHeader)
, errorDescriptionMatch = /error_description="(.*?)"/.exec(authenticateHeader)
;
hueError = new HueError({
type: response.status,
message: (errorMatch ? errorMatch[1] : response.data) || 'Error',
description: errorDescriptionMatch ? errorDescriptionMatch[1] : null,
address: response.config.url,
});
} else {
hueError = new HueError({
type: response.status,
error: response.data || 'Error',
address: response.config.url,
});
}
const error = new ApiError(hueError);
// error.stack = err.stack;
return error;
}