/** * HTTP Client Module * This module provides a stateless, function-based API for making HTTP requests. * Each request function (get, post, request, etc.) creates an isolated, * short-lived native client instance, ensuring thread-safety and preventing state conflicts. * * Features: * - GET/POST/PUT/PATCH/DELETE requests * - File upload/download with progress * - HTTPS with certificate verification options (disabled by default) * - Fully configurable via an options object per request * * Usage: * - Simple requests: `httpclient.get(url, { timeout: 3000 })` * - Complex requests: `httpclient.request({ url, method, headers: { 'Content-Type': 'application/json' }, body, ... })` * * Doc/Demo : https://github.com/DejaOS/DejaOS */ import * as native from './libvbar-m-dxhttpclient.so' const httpclient = {} /** * The core request function. Each call invokes the native stateless request function. * @param {object} options - Request options. * @param {string} options.url - The request URL. (Required) * @param {string} [options.method='GET'] - The request method (GET, POST, etc.). * @param {object|string[]} [options.headers] - Request headers as a key-value object (recommended) or an array of strings for backward compatibility. * @param {string|object} [options.body] - The request body. JS objects will be stringified to JSON. * @param {number} [options.timeout=5000] - Timeout in milliseconds. * @param {Function} [options.onProgress] - Progress callback function. * @param {number} [options.verifyPeer] - Certificate verification (0: disable, 1: enable). Default: 0 (disabled for convenience in IoT). * @param {number} [options.verifyHost] - Hostname verification (0: disable, 2: enable). Default: 0 (disabled for convenience in IoT). * @param {string} [options.caFile] - Path to CA certificate file. * @returns {object} Response result { code, status, message, data }. * @throws {Error} Throws error on invalid input. */ httpclient.request = function (options) { if (!options || typeof options !== 'object') { throw new Error("Request options object is required"); } if (!options.url) { throw new Error("options.url is required"); } const optsToSet = { ...options }; // Default method to GET if (!optsToSet.method) { optsToSet.method = 'GET'; } // Default timeout if (optsToSet.timeout === undefined) { optsToSet.timeout = 5000; } // Convert headers object to array of strings for C layer BEFORE further processing if (optsToSet.headers && typeof optsToSet.headers === 'object' && !Array.isArray(optsToSet.headers)) { optsToSet.headers = Object.entries(optsToSet.headers).map(([key, value]) => `${key}: ${value}`); } if (!optsToSet.headers) { optsToSet.headers = []; } // Auto-stringify JSON body and set Content-Type header if (optsToSet.body && typeof optsToSet.body === 'object') { optsToSet.body = JSON.stringify(optsToSet.body); // Add header only if not already present if (!optsToSet.headers.some(h => h.toLowerCase().startsWith('content-type:'))) { optsToSet.headers.push('Content-Type: application/json'); } } return native.request(optsToSet); } /** * Send GET request. * @param {string} url - Request URL. * @param {number} [timeout=5000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options (headers, etc.). * @returns {object} Response result. */ httpclient.get = function (url, timeout = 5000, options = {}) { return httpclient.request({ ...options, url: url, method: 'GET', timeout: timeout }); } /** * Send POST request with a body. * @param {string} url - Request URL. * @param {string|object} data - Request body. JS objects will be stringified as JSON. * @param {number} [timeout=5000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options (headers, timeout, etc.). * @returns {object} Response result. */ httpclient.post = function (url, data, timeout = 5000, options = {}) { return httpclient.request({ ...options, url: url, method: 'POST', body: data, timeout: timeout }); } /** * Send PUT request with a body. * @param {string} url - Request URL. * @param {string|object} data - Request body. JS objects will be stringified as JSON. * @param {number} [timeout=5000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options. * @returns {object} Response result. */ httpclient.put = function (url, data, timeout = 5000, options = {}) { return httpclient.request({ ...options, url: url, method: 'PUT', body: data, timeout: timeout }); } /** * Send PATCH request with a body. * @param {string} url - Request URL. * @param {string|object} data - Request body. JS objects will be stringified as JSON. * @param {number} [timeout=5000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options. * @returns {object} Response result. */ httpclient.patch = function (url, data, timeout = 5000, options = {}) { return httpclient.request({ ...options, url: url, method: 'PATCH', body: data, timeout: timeout }); } /** * Send DELETE request. * @param {string} url - Request URL. * @param {number} [timeout=5000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options. * @returns {object} Response result. */ httpclient.delete = function (url, timeout = 5000, options = {}) { return httpclient.request({ ...options, url: url, method: 'DELETE', timeout: timeout }); } /** * Download a file. * @param {string} url - Request URL. * @param {string} localPath - Local path to save the file. * @param {number} [timeout=30000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options. * @returns {object} Response result (without data field). */ httpclient.download = function (url, localPath, timeout = 30000, options = {}) { if (!url) throw new Error("URL is required"); if (!localPath) throw new Error("Local path is required"); const optsToSet = { ...options, timeout: timeout, // Longer default timeout for downloads url: url, method: 'GET' }; // Convert headers object to array of strings for C layer if (optsToSet.headers && typeof optsToSet.headers === 'object' && !Array.isArray(optsToSet.headers)) { optsToSet.headers = Object.entries(optsToSet.headers).map(([key, value]) => `${key}: ${value}`); } return native.download(localPath, optsToSet); } /** * Upload a file. * @param {string} url - Request URL. * @param {string} localPath - Local path of the file to upload. * @param {number} [timeout=30000] - Timeout in milliseconds. For backward compatibility. * @param {object} [options] - Additional request options. * @returns {object} Response result. */ httpclient.upload = function (url, localPath, timeout = 30000, options = {}) { if (!url) throw new Error("URL is required"); if (!localPath) throw new Error("Local path is required"); const optsToSet = { ...options, timeout: timeout, // Longer default timeout for uploads url: url, method: 'POST' }; // Convert headers object to array of strings for C layer if (optsToSet.headers && typeof optsToSet.headers === 'object' && !Array.isArray(optsToSet.headers)) { optsToSet.headers = Object.entries(optsToSet.headers).map(([key, value]) => `${key}: ${value}`); } return native.upload(localPath, optsToSet); } // #region Deprecated Functions // The following functions are deprecated and will be removed in a future version. // They are kept for backward compatibility to guide users to the new stateless API. const DEPRECATION_ERROR_MSG = "The stateful API (init, deinit, setOpt, reset) is deprecated. Please use the new stateless, function-based API by passing all options in a single object to methods like request(), get(), post(), etc."; /** * @deprecated Use the new stateless API. */ httpclient.init = function () { // This function is now a no-op, but we don't throw an error // to allow old code to run without crashing, though it's discouraged. } /** * @deprecated Use the new stateless API. */ httpclient.deinit = function () { // This function is now a no-op. } /** * @deprecated Use the new stateless API by passing options to request() or other methods. */ httpclient.setOpt = function () { throw new Error(DEPRECATION_ERROR_MSG); } /** * @deprecated Use the new stateless API. Each call is already isolated. */ httpclient.reset = function () { throw new Error(DEPRECATION_ERROR_MSG); } // #endregion export default httpclient;