UNPKG

@digitalbooting/request-api

Version:

Light Weight Request Api for Http requests support GraphQL and Rest

292 lines (251 loc) 7.5 kB
import HttpCrypt from "./crypt"; class ApiClient extends HttpCrypt { #encryption = false; #initialHeaders = {}; constructor(baseURL, headers = {}, options = {}) { super(); this.baseURL = baseURL || ""; this.#initialHeaders = headers; this.headers = { ...this.#initialHeaders }; this.options = { disableDefaultGraphqlEndpoint: false, ...options }; this.middlewares = []; } get encryption_enabled() { return this.#encryption; } get options_enabled() { return this.options; } get headers_enabled() { return this.headers; } get middlewares_enabled() { return this.middlewares; } get base_url() { return this.baseURL; } get key() { return this._key; } get iv() { return this._iv; } // Agregar y ejecutar middlewares registerMiddleware(fn) { this.middlewares.push(fn); return this; } enableEncryption(key = null, iv = null) { if (key) this.setKey(key); // Sobrescribir la clave si se pasa una if (iv) this.setIV(iv); // Sobrescribir el IV si se pasa uno this.#encryption = true; return this; } async execMiddlewares() { for (const middleware of this.middlewares) { const result = await middleware(); if (!result) return false; } return true; } // Setter para las opciones setOptions(newOptions) { this.options = { ...this.options, ...newOptions }; return this; } // Setter para los headers setHeaders(newHeaders) { this.headers = { ...this.headers, ...newHeaders }; return this; } // reset para los headers setDefaultHeaders() { this.headers = this.#initialHeaders; return this; } // Método genérico de autenticación setAuth(type, token) { const authType = type === "bearer" ? `Bearer ${token}` : `Basic ${btoa(token)}`; this.setHeaders({ Authorization: authType }); return this; } // Checkear JWT con renovación automática de token async checkJWToken(token) { try { const response = await fetch(`${this.baseURL}/check-jwt`, { method: "GET", headers: { ...this.headers, Authorization: `Bearer ${token}` }, }); if (!response.ok || response.status === 401) { // Lógica para renovar token const newToken = await this.renewToken(); if (newToken) { this.setBearerAuth(newToken); return [true, null]; } return [false, "Session expired"]; } return [true, null]; } catch (error) { return [false, error.message]; } } // Método para renovar el token (Ejemplo simple) async renewToken() { const response = await fetch(`${this.baseURL}/renew-token`, { method: "POST", headers: this.headers, }); if (response.ok) { const data = await response.json(); return data.token; } return null; } // Método de petición genérico que ejecuta middlewares y maneja errores async request(path, body = null, isFormData = false, pure_post = false) { const proceed = await this.execMiddlewares(); if (!proceed) { throw new Error("Request blocked by middleware"); } const url = `${this.baseURL}${path}`; const headers = this.headers; const options = { headers, ...this.options, }; if (body && !isFormData) { options.body = JSON.stringify(body); } else if (body && isFormData) { options.body = body; // Enviar `FormData` directamente } try { const response = await fetch(url, options); if (pure_post) { return [ { data: {}, success: response.ok, status: response.status, }, null, ]; } const data = await response.json(); if (!response.ok) { throw new Error(`${JSON.stringify({ response, data })}`); } // Desencriptar la respuesta si la encriptación está habilitada if (this.encryption_enabled && data.encrypted_response) { // const decryptedResponse = this.decrypt(data.payload); // return { // success: true, // data: JSON.parse(decryptedResponse), // status: response.status, // }; } return [ { data, success: true, status: response.status, }, null, ]; } catch (error) { const payloadIsValid = this.validatePatyloadError(error.message); if (payloadIsValid) { const { response, data } = JSON.parse(error.message); console.error(":: Error Api Response :: " + { response, data }); return [ null, { data, response, success: false, status: response.status, }, ]; } console.error(error); return [null, error]; } finally { this.setDefaultHeaders(); } } // Métodos específicos para cada tipo de solicitud HTTP async get(path) { return this.setOptions({ method: "GET" }).request(path); } async post(path, body, isFormData = false) { return this.setOptions({ method: "POST" }).request(path, body, isFormData); } async put(path, body, isFormData = false) { return this.setOptions({ method: "PUT" }).request(path, body, isFormData); } async delete(path) { return this.setOptions({ method: "DELETE" }).request(path); } async pure_post(path, body, isFormData = false) { return this.setOptions({ method: "POST" }).request( path, body, isFormData, true ); } // Método para peticiones GraphQL async graphql(query, variables = {}, method = "POST") { let options = {}; let body = { query, variables, }; // Encriptar la query si la encriptación está habilitada if (this.encryption_enabled) { const plainTextBody = JSON.stringify(body); const encryptedQuery = this.encrypt(plainTextBody); options = { ...options, "X-Channel": this.iv, "X-Fingerprint": this.key, }; body = { ...body, query: encryptedQuery }; } const endpoint = this.options.disableDefaultGraphqlEndpoint ? "" : "/graphql"; return this.setOptions({ ...options, method }).request(endpoint, body); } // Obtener la IP del cliente async requestIp() { try { const response = await fetch("https://api.ipify.org?format=json"); const res = await response.json(); return res; } catch (error) { return { ip: "0.0.0.0" }; } } validatePatyloadError(str) { try { JSON.parse(str); return true; // El string es un JSON válido } catch (e) { return false; // No es un JSON válido } } } /** * * @param {String} baseUrl - String de url base para las peticiones * @param {Object} headers - Objeto de headers para las solicitudes * @param {Object} options - Objeto de opciones adicionales como method, mode, etc. * @returns */ export const createClient = (baseUrl, headers = {}, options = {}) => new ApiClient(baseUrl, headers, options); export { gql } from "./utils"; export default ApiClient;