import axios from 'axios'; import type { key_val } from '$lib/ae_stores'; export let temp_get_blob_percent_completed = 0; // export let get_blob_percent_completed = readable(temp_get_blob_percent_completed); export let 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 let get_object_percent_completed = temp_get_object_percent_completed; // Updated 2024-05-23 export let 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']}`); console.log('API Config:', api_cfg); } if (log_lvl > 2) { console.log(`Return Meta: ${return_meta}`); console.log(`Return Blob: ${return_blob}`); console.log(`Filename: ${filename}`); console.log(`Auto Download: ${auto_download}`); } } if (!api_cfg) { console.log('No API Config was provided. Returning false.'); return false; } let 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_!') let headers_cleaned: key_val = {}; for (const prop in headers) { // No underscores allowed in the header parameters! let 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'; } } if (!return_blob) { let response_data_promise = await axios_api.get( endpoint, { headers: headers, params: params, onDownloadProgress: (progressEvent) => { let 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'); } let return_data = []; return_data.push(response.data['data']); return return_data; } else if (response.data['data']) { let 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 { let 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) { // 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); } 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.'); } 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 unknown. This should not happen in most cases.'); Promise.reject(new Error('fail')).then(resolved, rejected); } } else { // console.log('Expecting a Blob to be returned...'); let response_data_promise = await axios_api.get( endpoint, { params: params, responseType: 'blob', onDownloadProgress: (progressEvent) => { let 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; } }); 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 { // 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); }