UNPKG

fritzbox-api

Version:

Straightforward, lightweight and extendable Node.js library to communicate with FRITZ!Box devices

143 lines (124 loc) 4.35 kB
// 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;