@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
184 lines (181 loc) • 7 kB
JavaScript
import superagent from 'superagent';
import Agent, { HttpsAgent } from 'agentkeepalive';
import http from 'http';
import https from 'https';
import { URL } from 'url';
import { pipeline } from 'stream';
import logger from '../utils/logger.js';
import sanitise from '../utils/sanitisedError.js';
class RestClient {
name;
config;
agent;
constructor(name, config) {
this.name = name;
this.config = config;
this.agent = config.url.startsWith('https') ? new HttpsAgent(config.agent) : new Agent(config.agent);
}
apiUrl() {
return this.config.url;
}
timeoutConfig() {
return this.config.agent.timeout;
}
async get(request) {
return this.getWithHeaders(request).then(result => result.data);
}
async getStream({ path = '', query = {}, headers = {}, token }, res) {
const url = new URL(`${this.apiUrl()}${path}`);
Object.entries(query).forEach(([key, value]) => {
if (value === undefined)
return;
if (Array.isArray(value)) {
value.forEach(v => url.searchParams.append(key, String(v)));
}
else {
url.searchParams.append(key, String(value));
}
});
const client = url.protocol === 'https:' ? https : http;
const req = client.request(url, {
method: 'GET',
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : undefined,
},
agent: this.agent,
}, upstream => {
// Forward status
res.status(upstream.statusCode || 500);
// Forward headers
Object.entries(upstream.headers).forEach(([key, value]) => {
if (value !== undefined) {
res.setHeader(key, value);
}
});
res.flushHeaders();
res.on('close', () => {
req.destroy();
});
pipeline(upstream, res, err => {
if (err) {
res.destroy(err);
}
});
});
req.setTimeout(this.timeoutConfig(), () => {
req.destroy(new Error('Upstream request timed out.'));
});
req.on('error', err => {
logger.warn({ err }, `Error streaming from ${this.name}, path: '${path}'`);
if (!res.headersSent) {
res.status(502).end('Download request failed');
}
else {
res.destroy(err);
}
});
req.end();
}
async requestWithBody(method, { path, query = {}, headers = {}, responseType = '', data = {}, raw = false, retry = false }, token) {
logger.info(`${this.name} ${method.toUpperCase()}: ${path}`);
logger.info(`info about request: ${method} | ${path} | ${JSON.stringify(data)} | ${JSON.stringify(query)}`);
try {
const result = await superagent[method](`${this.apiUrl()}${path}`)
.query(query)
.send(data)
.agent(this.agent)
.retry(2, err => {
if (retry === false) {
return false;
}
if (err)
logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
return undefined; // retry handler only for logging retries, not to influence retry logic
})
.auth(token, { type: 'bearer' })
.set(headers)
.responseType(responseType)
.timeout(this.timeoutConfig());
return raw ? result : result.body;
}
catch (error) {
if (!(error instanceof Error)) {
throw error;
}
const sanitisedError = sanitise(error);
logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: '${method.toUpperCase()}'`);
throw sanitisedError;
}
}
async post(request, token) {
return this.requestWithBody('post', request, token);
}
async getWithHeaders({ path = '', query = {}, headers = {}, responseType = '', raw = false, token, }) {
const loggerData = {
path: `${this.config.url}${path}`,
...(Object.keys(query).length && { query }),
};
logger.info(`${this.name}: ${JSON.stringify(loggerData)}`);
try {
const result = await superagent
.get(`${this.apiUrl()}${path}`)
.agent(this.agent)
.retry(2, err => {
if (err)
logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
return undefined; // retry handler only for logging retries, not to influence retry logic
})
.query(query)
.auth(token, { type: 'bearer' })
.set(headers)
.responseType(responseType)
.timeout(this.timeoutConfig());
return {
data: raw ? result : result.body,
headers: result.headers,
};
}
catch (error) {
const sanitisedError = sanitise(error);
logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`);
throw sanitisedError;
}
}
async deleteWithHeaders({ path = '', query = {}, headers = {}, responseType = '', raw = false, token, }) {
const loggerData = {
path: `${this.config.url}${path}`,
query,
};
logger.info(`${this.name}: ${JSON.stringify(loggerData)}`);
try {
const result = await superagent
.delete(`${this.apiUrl()}${path}`)
.agent(this.agent)
.retry(2, err => {
if (err)
logger.info(`Retry handler found API error with ${err.code} ${err.message}`);
return undefined; // retry handler only for logging retries, not to influence retry logic
})
.query(query)
.auth(token, { type: 'bearer' })
.set(headers)
.responseType(responseType)
.timeout(this.timeoutConfig());
return {
data: raw ? result : result.body,
headers: result.headers,
};
}
catch (error) {
const sanitisedError = sanitise(error);
logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`);
throw sanitisedError;
}
}
async delete(request) {
return this.deleteWithHeaders(request).then(result => result.data);
}
}
export { RestClient, RestClient as default };
//# sourceMappingURL=restClient.js.map