import axios from 'axios'; import type { key_val } from '$lib/stores/ae_stores'; export let temp_get_blob_percent_completed = 0; // export let get_blob_percent_completed = readable(temp_get_blob_percent_completed); export const get_blob_percent_completed = temp_get_blob_percent_completed; export let temp_get_object_percent_completed = 0; // export let get_object_percent_completed = readable(temp_get_object_percent_completed); export const get_object_percent_completed = temp_get_object_percent_completed; // Updated 2024-05-23 export const get_object = async function get_object({ api_cfg = null, endpoint = '', headers = {}, params = {}, data = {}, timeout = 60000, return_meta = false, return_blob = false, filename = '', auto_download = false, as_list = false, // The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event. task_id = crypto.randomUUID(), log_lvl = 0 }: { api_cfg: any; endpoint: string; headers?: any; params?: any; data?: any; timeout?: number; return_meta?: boolean; return_blob?: boolean; filename?: null | string; auto_download?: boolean; as_list?: boolean; task_id?: string; log_lvl?: number; }) { if (log_lvl) { console.log(`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`); console.log('Params:', params); if (log_lvl > 1) { console.log('Data:', data); console.log(`Base URL: ${api_cfg['base_url']}; Timeout: ${timeout}`); console.log('API Config:', api_cfg); } if (log_lvl > 2) { console.log( `Return Meta: ${return_meta}; Return Blob: ${return_blob}; Filename: ${filename}; Auto Download: ${auto_download}` ); } } if (!api_cfg) { console.log('No API Config was provided. Returning false.'); return false; } const axios_api = axios.create({ baseURL: api_cfg['base_url'], timeout: timeout // in milliseconds; 60000 = 60 seconds /* other custom settings */ }); axios_api.defaults.headers = api_cfg['headers']; if (log_lvl) { console.log('axios_api.defaults.headers:', axios_api.defaults.headers); console.log('Additional headers:', headers); } // console.log('Clean the headers. No _underscores_!') const headers_cleaned: key_val = {}; for (const prop in headers) { // No underscores allowed in the header parameters! const prop_cleaned = prop.replaceAll('_', '-'); // The value must be a string for the header! if (typeof headers[prop] != 'string') { headers[prop] = JSON.stringify(headers[prop]); } headers_cleaned[prop_cleaned] = headers[prop]; if (log_lvl) { console.log(`${prop_cleaned}: ${headers_cleaned[prop_cleaned]}`); } } headers = headers_cleaned; if (log_lvl) { console.log('All headers cleaned:', headers); } if (log_lvl) { console.log('URL params:'); } for (const prop in params) { if (log_lvl > 1) { console.log(`URL param: ${prop}: ${params[prop]}`); } if (params[prop] === null) { params[prop] = 'null'; } } // Handle the case where there is no Blob expected to be returned. Mainly JSON and text data. if (!return_blob) { const response_data_promise = await axios_api .get(endpoint, { headers: headers, params: params, onDownloadProgress: (progressEvent) => { const percent_completed = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); if (log_lvl > 1) { console.log( 'GET Data Progress:', progressEvent.progress, 'Total:', progressEvent.total, 'Loaded:', progressEvent.loaded, 'Percent Completed', percent_completed ); } temp_get_object_percent_completed = percent_completed; // WARNING: This needs to be tied to an object type and ID. This is a temporary solution. try { // Check if window is defined. This is to prevent errors in SvelteKit. if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_data', status: 'downloading', task_id: task_id, endpoint: endpoint, filename: filename, size_total: progressEvent.total, size_loaded: progressEvent.loaded, percent_completed: percent_completed }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } } }) .then(function (response) { if (log_lvl) { console.log( `GET Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${JSON.stringify(response.config.params)}` ); } if (log_lvl > 1) { console.log('GET Response:', response); } // Post file download message try { if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_data', status: 'complete', task_id: task_id, endpoint: endpoint, filename: filename, size_total: 0, size_loaded: 0, percent_completed: 100 }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } if (!Array.isArray(response.data['data']) && as_list) { if (log_lvl) { console.log( 'Data result is a dictionary/object, not an array/list. Forcing return as an array/list' ); } const return_data = []; return_data.push(response.data['data']); return return_data; } else if (response.data['data']) { const return_data = response.data['data']; if (log_lvl) { if (Array.isArray(return_data)) { console.log( `Data result is an array/list. Array length: ${return_data.length}` ); } else { console.log(`Data result is a dictionary/object, not an array/list.`); } } return return_data; } else { const return_data = response.data; if (log_lvl) { if (Array.isArray(return_data)) { console.log( `Not a standard response from Aether's API. Data result is an array/list. Array length: ${return_data.length}` ); } else { console.log( `Not a standard response from Aether's API. Data result is a dictionary/object, not an array/list.` ); } } return return_data; } }) .catch(function (error: any) { // Handle the common and expected 404 "error" first if (error.response && error.response.status === 404) { if (log_lvl) { console.log('The response was a 404 not found "error". Returning null.'); } if (log_lvl > 1) { console.log(error.response); } if (log_lvl > 2) { console.log(error); } // Post file download message try { if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_data', status: 'complete', task_id: task_id, endpoint: endpoint, filename: filename, size_total: 0, size_loaded: 0, percent_completed: 0 }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } return null; // Returning null since there were no results } if (log_lvl) { console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`); console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion??? if (error.response) { // The request was made and the server responded with a status code that falls out of the range of 2xx console.log('Error Response Data', error.response.data); console.log('Error Response Status', error.response.status); console.log('Error Response Headers', error.response.headers); } else if (error.request) { // The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js if (log_lvl > 1) { console.log('Error Request', error.request); } } else { // Something happened in setting up the request that triggered an Error console.log('Error Message', error.message); } } if (log_lvl > 2) { console.log('Error:', error); console.log(error.config); } if (error.code === 'ECONNABORTED') { // Timeout Error (You can implement retry here where suitable) console.log('Timeout Error: ', error.message); } if (log_lvl) { console.log('The response was an error. Returning false.'); } return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns. // return error; }); if (log_lvl > 1) { // console.log(`Response Data: ${response_data_promise}`); console.log(`Response Data:`, response_data_promise); // console.log(response_data_promise); } if (response_data_promise) { // The most common and expected response. // console.log('Returning result. This is generally expected.'); return response_data_promise; } else if (response_data_promise === null) { // Less common, but expected response if no results were returned. if (log_lvl) { console.log('Returning null. This is expected if no results were found. (404)'); } return response_data_promise; } else if (response_data_promise === false) { // Not common, but expected response if the request to the API had an issue. console.log('Returning false. There may have been an issue with this request.'); return response_data_promise; } else { // This generally should not happen. It likely means the query was bad or an API issue. console.log('Returning (JSON/text) unknown. This should not happen in most cases.'); Promise.reject(new Error('fail')).then(resolved, rejected); } // Handle the case where a Blob is expected to be returned. } else { // console.log('Expecting a Blob to be returned...'); const response_data_promise = await axios_api .get(endpoint, { params: params, responseType: 'blob', onDownloadProgress: (progressEvent) => { const percent_completed = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); console.log( 'GET Blob Progress:', progressEvent.progress, 'Total:', progressEvent.total, 'Loaded:', progressEvent.loaded, 'Percent Completed', percent_completed ); temp_get_blob_percent_completed = percent_completed; // WARNING: This needs to be tied to an object type and ID. This is a temporary solution. try { if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_blob', status: 'downloading', task_id: task_id, endpoint: endpoint, filename: filename, size_total: progressEvent.total, size_loaded: progressEvent.loaded, percent_completed: percent_completed }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } } }) .then(function (response) { if (log_lvl) { console.log( `GET (blob) Response: status=${response.status} statusText=${response.statusText} baseURL=${response.config.baseURL} url=${response.config.url} method=${response.config.method} headers=${response.config.headers} params=${response.config.params}` ); } if (log_lvl > 1) { console.log('GET (blob) Response:', response); } const { data, headers } = response; // Careful if this download filename needs to be changed to a different file extension. The browser/client may not know how to handle it. if (filename) { } else if (headers['content-disposition']) { filename = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1'); } else { filename = 'unknown_file.ext'; } // WARNING: This needs to be tied to an object type and ID. This is a temporary solution. try { if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_blob', status: 'complete', task_id: task_id, endpoint: endpoint, filename: filename, size_total: 0, size_loaded: 0, percent_completed: 100 }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } if (auto_download) { if (log_lvl) { console.log(`Auto Download: ${filename}`); } const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); return true; } else { return response; } }) .catch(function (error: any) { // Handle the common and expected 404 "error" first if (error.response && error.response.status === 404) { if (log_lvl) { console.log('The response was a 404 not found "error". Returning null.'); } if (log_lvl > 1) { console.log(error.response); } if (log_lvl > 2) { console.log(error); } // Post file download message try { if (typeof window !== 'undefined') { window.postMessage( { type: 'api_download_blob', status: 'complete', task_id: task_id, endpoint: endpoint, filename: filename, size_total: 0, size_loaded: 0, percent_completed: 0 }, '*' ); } } catch (error) { console.log('Error posting message to window:', error); } return null; // Returning null since there were no results } if (log_lvl) { console.log(`Base URL: ${api_cfg['base_url']} | Endpoint: ${endpoint}`); console.log('Error Message:', error.message); // Is this needed here or below in the in the else portion??? if (error.response) { // The request was made and the server responded with a status code that falls out of the range of 2xx console.log('Error Response Data', error.response.data); console.log('Error Response Status', error.response.status); console.log('Error Response Headers', error.response.headers); } else if (error.request) { // The request was made but no response was received `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js if (log_lvl > 1) { console.log('Error Request', error.request); } } else { // Something happened in setting up the request that triggered an Error console.log('Error Message', error.message); } } if (error.code === 'ECONNABORTED') { // Timeout Error (You can implement retry here where suitable) console.log('Timeout Error: ', error.message); } if (log_lvl) { console.log('The response was an error. Returning false.'); } return false; // Returning false since something may have gone wrong. This includes timeouts. Also more in line with what the API returns. // return error; }); if (response_data_promise) { // The most common and expected response. // console.log('Returning result. This is generally expected.'); // let test_blob = new Blob([response_data_promise.data]); // console.log(test_blob); // return test_blob; // console.log(response_data_promise.blob()); return response_data_promise; } else if (response_data_promise === null) { // Less common, but expected response if no results were returned. if (log_lvl) { console.log('Returning null. This is expected if no results were found. (404)'); } return response_data_promise; } else if (response_data_promise === false) { // Not common, but expected response if the request to the API had an issue. console.log('Returning false. There may have been an issue with this request.'); return response_data_promise; } else { // This generally should not happen. It likely means the query was bad or an API issue. console.log('Returning (blob) unknown. This should not happen in most cases.'); Promise.reject(new Error('fail')).then(resolved, rejected); } } }; function resolved(result: any) { console.log('Resolved'); } function rejected(result: any) { console.error(result); }