import axios, { AxiosPromise, AxiosRequestConfig, AxiosProgressEvent } from 'axios'; import Config from '../config/Config'; import HTTP_METHODS from '../constants/HttpMethods'; import Route from '../routes/interface'; import Response from './Response'; import * as crypto from 'crypto'; class Requests { private static config: Config; private static baseUrl = ""; private static authToken = ""; private static community_id?: string = ""; constructor(config: Config) { Requests.config = config; } public static setBaseUrl(url: string) { Requests.baseUrl = url; } public static setAuthToken(token: string) { Requests.authToken = token; } public static setCommunityID(community_id: string | undefined) { Requests.community_id = community_id; } /** * Build an absolute API URL using the currently configured base URL. * * This is useful for browser primitives such as EventSource that need a URL * string instead of an Axios request wrapper. */ public static buildUrl(url: string, params?: Record): string { let path = url; if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .filter(([, value]) => value !== undefined && value !== null && value !== '') .map(([key, value]) => { if (Array.isArray(value)) { return value.map((item) => `${key}[]=${encodeURIComponent(item)}`).join('&'); } return `${key}=${encodeURIComponent(value)}`; }) .filter(Boolean) .join('&'); if (queryString) { path = `${path}${path.includes('?') ? '&' : '?'}${queryString}`; } } return Requests.baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, ''); } private static request( method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', url: string, data?: any, fileData?: any ): AxiosPromise> { let headers: { [key: string]: string } = { 'Content-Type': 'application/json', }; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } if (fileData) { headers['Content-Type'] = 'multipart/form-data'; } url = url.replace(/\/\//g, '/'); const uri = `${Requests.baseUrl}${url}`; const validUri = uri.replace(/\/\//g, '/'); const axiosPromise = axios({ method, url: uri, data: fileData || data, headers, }); return axiosPromise; } public static get(url: string, params?: Record): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => { if (Array.isArray(value)) { return value.map((item) => `${key}[]=${encodeURIComponent(item)}`).join('&'); } return `${key}=${encodeURIComponent(value)}`; }) .join('&'); url = `${url}?${queryString}`; } if (Requests.community_id) { const separator = url.includes('?') ? '&' : '?'; url = `${url}${separator}community_id=${Requests.community_id}`; } return Requests.request('GET', url); } public static download(url: string, params?: Record): AxiosPromise { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .filter(([, value]) => value !== undefined && value !== null && value !== '') .map(([key, value]) => { if (Array.isArray(value)) { return value.map((item) => `${key}[]=${encodeURIComponent(item)}`).join('&'); } return `${key}=${encodeURIComponent(value)}`; }) .filter(Boolean) .join('&'); if (queryString) { url = `${url}?${queryString}`; } } if (Requests.community_id) { const separator = url.includes('?') ? '&' : '?'; url = `${url}${separator}community_id=${Requests.community_id}`; } const headers: { [key: string]: string } = {}; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } const uri = Requests.baseUrl.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, ''); return axios({ method: 'GET', url: uri, headers, responseType: 'blob', }); } public static post(url: string, data: any, params?: Record): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } if (Requests.community_id) { data = { ...data, communities: [Requests.community_id], }; } return Requests.request('POST', url, data); } public static put(url: string, data: any, params?: Record): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } if (Requests.community_id) { data = { ...data, community_id: Requests.community_id, }; } return Requests.request('PUT', url, data); } public static patch(url: string, data: any, params?: Record): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } if (Requests.community_id) { data = { ...data, community_id: Requests.community_id, }; } return Requests.request('PATCH', url, data); } public static delete(url: string, params?: Record): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } if (Requests.community_id) { const separator = url.includes('?') ? '&' : '?'; url = `${url}${separator}community_id=${Requests.community_id}`; } return Requests.request('DELETE', url); } public static uploadFile( url: string, filename: string, file: File | Blob, data?: any, params?: Record, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void, options?: Pick & { excludeCommunityContext?: boolean } ): AxiosPromise> { // Process URL and params if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } // Prepare FormData const formData = new FormData(); formData.append(filename, file); if (Requests.community_id && !options?.excludeCommunityContext) { data = { ...data, communities: [Requests.community_id], }; } for (let key in data) { formData.append(key, data[key]); } // Prepare headers let headers: { [key: string]: string } = {}; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } // Format URL correctly const uri = Requests.baseUrl.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, ''); // Make the request return axios({ method: 'POST', url: uri, data: formData, headers, onUploadProgress, signal: options?.signal, timeout: options?.timeout, }); } public static postFormData( url: string, formData: FormData, params?: Record, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void ): AxiosPromise> { if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .filter(([, value]) => value !== undefined && value !== null && value !== '') .map(([key, value]) => { if (Array.isArray(value)) { return value.map((item) => `${key}[]=${encodeURIComponent(item)}`).join('&'); } return `${key}=${encodeURIComponent(value)}`; }) .filter(Boolean) .join('&'); if (queryString) { url = `${url}?${queryString}`; } } let headers: { [key: string]: string } = {}; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } const uri = Requests.baseUrl.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, ''); return axios({ method: 'POST', url: uri, data: formData, headers, onUploadProgress, }); } public static uploadBlob( url: string, filename: string, blob: Blob, data?: any, params?: Record, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void ): AxiosPromise> { // Process URL and params if (params && Object.keys(params).length > 0) { const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); url = `${url}?${queryString}`; } // Prepare FormData const formData = new FormData(); formData.append(filename, blob); if (Requests.community_id) { data = { ...data, communities: [Requests.community_id], }; } for (let key in data) { formData.append(key, data[key]); } // Prepare headers let headers: { [key: string]: string } = {}; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } // Format URL correctly const uri = Requests.baseUrl.replace(/\/+$/, '') + '/' + url.replace(/^\/+/, ''); // Make the request return axios({ method: 'POST', url: uri, data: formData, headers, onUploadProgress, }); } // Method adapted for browser environments public static async uploadFileInChunks( file: File, uploadUrl: string, onProgress?: (totalSize: number, amountUploaded: number) => void, data?: any, chunkSize?: number, // Default chunk size of 1MB ): Promise { if (!chunkSize) { chunkSize = 1024 * 1024 } const fileSize = file.size; const totalChunks = Math.ceil(fileSize / chunkSize); let currentChunkIndex = 0; let totalUploaded = 0; // Keep track of the total uploaded bytes // Generate a unique identifier for this upload session using the Web Cryptography API const array = new Uint32Array(4); window.crypto.getRandomValues(array); const identifier = Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join(''); while (currentChunkIndex <= totalChunks) { const start = currentChunkIndex * chunkSize; const end = Math.min(start + chunkSize, fileSize); const chunk = file.slice(start, end); const formData = new FormData(); formData.append('video', chunk, file.name); formData.append('chunkIndex', currentChunkIndex.toString()); formData.append('totalChunks', totalChunks.toString()); formData.append('identifier', identifier); // If there's additional data, append each key-value pair to the formData if (data) { for (const key in data) { formData.append(key, data[key]); } } // Construct the full URL if necessary or use a method to determine the base URL const fullUploadUrl = `${Requests.baseUrl}${uploadUrl}`; // Make sure the authorization token is included if required const headers: { [key: string]: string } = {}; if (Requests.authToken) { headers['Authorization'] = `Bearer ${Requests.authToken}`; } // Perform the upload await axios.post(fullUploadUrl, formData, { headers, onUploadProgress: (progressEvent) => { const currentChunkProgress = progressEvent.loaded; // Bytes uploaded of the current chunk // Calculate the total uploaded size including previous chunks and the current chunk's progress const totalProgress = totalUploaded + currentChunkProgress; if (onProgress) { onProgress(fileSize, end); } } }); currentChunkIndex++; } } public static processRoute(route: Route, data?: object, routeReplace?: { [key: string]: any }, params?: Record, options?: Pick & { excludeCommunityContext?: boolean }): AxiosPromise> { let url = route.url; if (routeReplace) { for (let key in routeReplace) { url = url.replace("{" + key + "}", routeReplace[key]); } } if (options) { const query = { ...params, ...(Requests.community_id && !options.excludeCommunityContext ? { community_id: Requests.community_id } : {}) }; return axios({ method: route.method, url: Requests.buildUrl(url, query), data, headers: { 'Content-Type': 'application/json', ...(Requests.authToken ? { Authorization: `Bearer ${Requests.authToken}` } : {}), ...options.headers }, signal: options.signal, timeout: options.timeout, }); } if (route.method == HTTP_METHODS.GET) { return Requests.get(url, params); } else if (route.method == HTTP_METHODS.POST) { return Requests.post(url, data, params); } else if (route.method == HTTP_METHODS.PATCH) { return Requests.patch(url, data, params); } else if (route.method == HTTP_METHODS.PUT) { return Requests.put(url, data, params); } else if (route.method == HTTP_METHODS.DELETE) { return Requests.delete(url, params); } return Requests.get(url); } } export default Requests;