/** * API Request Definition * This utility is responsible for defining how API requests should be made. * @author [Vivek Sudarsan] * @version 1.0.0 */ // Import helpers and constants import { hashWithSalt, avinaLog } from '../../util/helpers'; // Import third party libraries import axios from 'axios'; import { SENSITIVE_DATA_KEYS } from '../../util/constants'; // Type definitions interface IRequestProps { resource: Array | string; id?: Array; method?: 'get' | 'post' | 'put' | 'patch' | 'delete'; data?: object; config?: object; keepAlive?: boolean; } // const isDevelopment = ['local', 'true'].includes( // process.env.NODE_ENV_LOCAL || '' // ); // Define API root URL let ROOT_URL: string = 'https://api.withmesh.com'; let LOCAL_ROOT_URL: string = 'https://localhost:3000'; const API_PREFIX: string = ''; axios.defaults.withCredentials = true; /** * Request header and url constructor * @param resource * @param id * @returns */ const requestUrlConstructor = ( resource: Array | string, id?: Array ) => { if (typeof resource === 'string') { return `${resource}`; } // get current workspace and attach to headers const workspaceData = localStorage.getItem('mesh-interactive-user-workspace'); if (workspaceData) { const workspace = JSON.parse(workspaceData); axios.defaults.headers.common['Mesh-Current-Workspace-Id'] = workspace.id; } return resource .map( (resource, idx) => `${resource}${id ? `/${id[idx] ? id[idx] + '/' : ''}` : '/'}` ) .join(''); }; /** * API request handler * @param request * @returns */ const apiRequest = async (request: IRequestProps) => { // Construct request URL with resources and ids const resourcePath = requestUrlConstructor(request.resource, request.id); // Handle any pre-flight data handling const requestData = await preFlightHandler(request.data); // make the request try { const requestUrl = `${ window.location.hostname === 'localhost' ? LOCAL_ROOT_URL : ROOT_URL }${API_PREFIX}/${resourcePath}`; if (request.keepAlive && navigator.sendBeacon) { // Use sendBeacon for keep-alive requests const blob = new Blob([JSON.stringify(requestData)], { type: 'application/json', }); const result = navigator.sendBeacon(requestUrl, blob); if (!result) { throw new Error('Failed to queue request with sendBeacon'); } return { success: true }; } else { // make the request const response = await axios({ method: request.method, url: requestUrl, data: requestData, }); return response.data; } } catch (error) { avinaLog(`API request error: ${error}`); throw error; } }; export default apiRequest; /** * Pre-flight handler to hash sensitive data * before sending to the server * @param request */ export const preFlightHandler = async (requestData: any) => { // hash sensitive data // if (requestData && glob.workspaceHashSalt) { // // make a copy of the request data // const requestDataHashed: any = cloneDeep(requestData); // // check each key of request data to see if it's // // in the sensitive data list and hash it if it is // await hashSensitiveData(requestDataHashed); // return requestDataHashed; // } return requestData; }; /** * Recursive function to hash sensitive data for * any level of depth in the object * @param obj The object to hash * @param hashWithSalt The function to hash data with salt */ const hashSensitiveData = async (obj: any) => { await Promise.all( Object.keys(obj).map(async key => { if (SENSITIVE_DATA_KEYS.includes(key)) { // Hash the value if the key is sensitive obj[key] = await hashWithSalt(obj[key]); } else if (typeof obj[key] === 'object' && obj[key] !== null) { await hashSensitiveData(obj[key]); } else if (typeof obj[key] === 'string') { try { const parsed = JSON.parse(obj[key]); // if able to parse, recursively call hashSensitiveData await hashSensitiveData(parsed); // stringify the resulting object obj[key] = JSON.stringify(parsed); } catch (e) {} } }) ); };