fritzbox-api
Version:
Straightforward, lightweight and extendable Node.js library to communicate with FRITZ!Box devices
143 lines (124 loc) • 4.35 kB
JavaScript
// Copyright (c) 2024, Thorsten A. Weintz. All rights reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.
import { stringify } from 'querystring';
import axios, { toFormData } from 'axios';
import DigestAuthHelper from './digestAuthBuilder.mjs';
/**
* HttpClient is an abstract base class to handle HTTP request.
*/
class HttpClient {
/**
* Initializes new instance of @see HttpClient.
*
* @param {string} baseUrl Base URL of device.
*/
constructor(baseUrl) {
this.instance = axios.create({
baseURL: baseUrl
});
}
/**
* Performs HTTP requests by passing configuration values
* to related HTTP client.
*
* @param {object} options Object with request configuration values.
* @param {object} auth Credentials used for authentication.
* @returns Object with response of HTTP client.
*/
async request(options, auth) {
this.reqDate = new Date();
try {
const response = await this.instance.request({
...options,
validateStatus: () => true
});
if (response.status === 401 && auth) {
return this.#retryWithDigest(auth, response, options);
}
return response;
} catch (error) {
this.reqDate = null;
return {
error: error.message
};
}
}
/**
* Retries the request with Digest Authentication if a 401 status is returned.
*
* @param {object} auth Credentials used for authentication.
* @param {*} prevResponse Previous response containing the www-authenticate header.
* @param {*} options Request configuration options.
* @returns Object with response of HTTP client.
*/
async #retryWithDigest(auth, prevResponse, options) {
const { username, password } = auth;
const digestHeader = prevResponse.headers["www-authenticate"];
if (!digestHeader || !username || !password) {
return prevResponse;
}
const digestData = DigestAuthHelper.parseDigest(digestHeader);
const authHeader = DigestAuthHelper.buildDigestAuthHeader(
{
username,
password,
method: options.method,
uri: new URL(options.url, this.instance.defaults.baseURL).pathname
},
digestData
);
return this.instance.request({
...options,
headers: {
...options.headers,
Authorization: authHeader
}
});
}
/**
* Performs GET request by given URL.
*
* @param {string} url Target URL of endpoint.
* @returns Object with response of HTTP client.
*/
get = async (url) => this.request({
method: 'GET',
url
});
/**
* Performs POST request by given URL and data.
*
* @param {string} url Target URL of endpoint.
* @param {object} data Data to be sent as body to server.
* @param {boolean} isFormData Contains whether data is formdata.
* @returns Object with response of HTTP client.
*/
post = async (url, data, isFormData) => this.request({
method: 'POST',
url,
data: isFormData ? toFormData(data) : stringify(data)
});
/**
* Sends a raw XML POST request (used for TR-064 SOAP API).
*
* @param {string} url TR-064 control URL (port 49000 / 49443).
* @param {string} xml SOAP XML payload.
* @param {object} auth redentials used for authentication.
* @param {object} headers Additional HTTP headers (e.g. SOAPAction).
* @returns Object with response of HTTP client.
*/
postSoap = async (url, xml, auth, headers = {}) =>
this.request({
method: 'POST',
url,
data: xml,
headers: {
'Content-Type': 'text/xml; charset=utf-8',
...headers
}
}, auth);
}
/**
* Exports @see HttpClient as default class.
*/
export default HttpClient;