tryitout-4.25.0.js (10372B)
1 window.abortControllers = {}; 2 3 function cacheAuthValue() { 4 // Whenever the auth header is set for one endpoint, cache it for the others 5 window.lastAuthValue = ''; 6 let authInputs = document.querySelectorAll(`.auth-value`) 7 authInputs.forEach(el => { 8 el.addEventListener('input', (event) => { 9 window.lastAuthValue = event.target.value; 10 authInputs.forEach(otherInput => { 11 if (otherInput === el) return; 12 // Don't block the main thread 13 setTimeout(() => { 14 otherInput.value = window.lastAuthValue; 15 }, 0); 16 }); 17 }); 18 }); 19 } 20 21 window.addEventListener('DOMContentLoaded', cacheAuthValue); 22 23 function getCookie(name) { 24 if (!document.cookie) { 25 return null; 26 } 27 28 const cookies = document.cookie.split(';') 29 .map(c => c.trim()) 30 .filter(c => c.startsWith(name + '=')); 31 32 if (cookies.length === 0) { 33 return null; 34 } 35 36 return decodeURIComponent(cookies[0].split('=')[1]); 37 } 38 39 function tryItOut(endpointId) { 40 document.querySelector(`#btn-tryout-${endpointId}`).hidden = true; 41 document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false; 42 const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false; 43 executeBtn.disabled = false; 44 45 // Show all input fields 46 document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`) 47 .forEach(el => el.style.display = 'block'); 48 49 if (document.querySelector(`#form-${endpointId}`).dataset.authed === "1") { 50 const authElement = document.querySelector(`#auth-${endpointId}`); 51 authElement && (authElement.hidden = false); 52 } 53 // Expand all nested fields 54 document.querySelectorAll(`#form-${endpointId} details`) 55 .forEach(el => el.open = true); 56 } 57 58 function cancelTryOut(endpointId) { 59 if (window.abortControllers[endpointId]) { 60 window.abortControllers[endpointId].abort(); 61 delete window.abortControllers[endpointId]; 62 } 63 64 document.querySelector(`#btn-tryout-${endpointId}`).hidden = false; 65 const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`); 66 executeBtn.hidden = true; 67 executeBtn.textContent = executeBtn.dataset.initialText; 68 document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true; 69 // Hide inputs 70 document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`) 71 .forEach(el => el.style.display = 'none'); 72 document.querySelectorAll(`#form-${endpointId} details`) 73 .forEach(el => el.open = false); 74 const authElement = document.querySelector(`#auth-${endpointId}`); 75 authElement && (authElement.hidden = true); 76 77 document.querySelector('#execution-results-' + endpointId).hidden = true; 78 document.querySelector('#execution-error-' + endpointId).hidden = true; 79 80 // Revert to sample code blocks 81 document.querySelector('#example-requests-' + endpointId).hidden = false; 82 document.querySelector('#example-responses-' + endpointId).hidden = false; 83 } 84 85 function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpointId = null) { 86 console.log({endpointId, path, body, query, headers}); 87 88 if (!(body instanceof FormData) && typeof body !== "string") { 89 body = JSON.stringify(body) 90 } 91 92 const url = new URL(window.tryItOutBaseUrl + '/' + path.replace(/^\//, '')); 93 94 // We need this function because if you try to set an array or object directly to a URLSearchParams object, 95 // you'll get [object Object] or the array.toString() 96 function addItemToSearchParamsObject(key, value, searchParams) { 97 if (Array.isArray(value)) { 98 value.forEach((v, i) => { 99 // Append {filters: [first, second]} as filters[0]=first&filters[1]second 100 addItemToSearchParamsObject(key + '[' + i + ']', v, searchParams); 101 }) 102 } else if (typeof value === 'object' && value !== null) { 103 Object.keys(value).forEach((i) => { 104 // Append {filters: {name: first}} as filters[name]=first 105 addItemToSearchParamsObject(key + '[' + i + ']', value[i], searchParams); 106 }); 107 } else { 108 searchParams.append(key, value); 109 } 110 } 111 112 Object.keys(query) 113 .forEach(key => addItemToSearchParamsObject(key, query[key], url.searchParams)); 114 115 window.abortControllers[endpointId] = new AbortController(); 116 117 return fetch(url, { 118 method, 119 headers, 120 body: method === 'GET' ? undefined : body, 121 signal: window.abortControllers[endpointId].signal, 122 referrer: window.tryItOutBaseUrl, 123 mode: 'cors', 124 credentials: 'same-origin', 125 }) 126 .then(response => Promise.all([response.status, response.statusText, response.text(), response.headers])); 127 } 128 129 function hideCodeSamples(endpointId) { 130 document.querySelector('#example-requests-' + endpointId).hidden = true; 131 document.querySelector('#example-responses-' + endpointId).hidden = true; 132 } 133 134 function handleResponse(endpointId, response, status, headers) { 135 hideCodeSamples(endpointId); 136 137 // Hide error views 138 document.querySelector('#execution-error-' + endpointId).hidden = true; 139 140 const responseContentEl = document.querySelector('#execution-response-content-' + endpointId); 141 142 // Prettify it if it's JSON 143 let isJson = false; 144 try { 145 const jsonParsed = JSON.parse(response); 146 if (jsonParsed !== null) { 147 isJson = true; 148 response = JSON.stringify(jsonParsed, null, 4); 149 } 150 } catch (e) { 151 152 } 153 responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response; 154 isJson && window.hljs.highlightElement(responseContentEl); 155 const statusEl = document.querySelector('#execution-response-status-' + endpointId); 156 statusEl.textContent = ` (${status})`; 157 document.querySelector('#execution-results-' + endpointId).hidden = false; 158 statusEl.scrollIntoView({behavior: "smooth", block: "center"}); 159 } 160 161 function handleError(endpointId, err) { 162 hideCodeSamples(endpointId); 163 // Hide response views 164 document.querySelector('#execution-results-' + endpointId).hidden = true; 165 166 // Show error views 167 let errorMessage = err.message || err; 168 const $errorMessageEl = document.querySelector('#execution-error-message-' + endpointId); 169 $errorMessageEl.textContent = errorMessage + $errorMessageEl.textContent; 170 const errorEl = document.querySelector('#execution-error-' + endpointId); 171 errorEl.hidden = false; 172 errorEl.scrollIntoView({behavior: "smooth", block: "center"}); 173 174 } 175 176 async function executeTryOut(endpointId, form) { 177 const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`); 178 executeBtn.textContent = executeBtn.dataset.loadingText; 179 executeBtn.disabled = true; 180 executeBtn.scrollIntoView({behavior: "smooth", block: "center"}); 181 182 let body; 183 let setter; 184 if (form.dataset.hasfiles === "1") { 185 body = new FormData(); 186 setter = (name, value) => body.append(name, value); 187 } else if (form.dataset.isarraybody === "1") { 188 body = []; 189 setter = (name, value) => _.set(body, name, value); 190 } else { 191 body = {}; 192 setter = (name, value) => _.set(body, name, value); 193 } 194 const bodyParameters = form.querySelectorAll('input[data-component=body]'); 195 bodyParameters.forEach(el => { 196 let value = el.value; 197 if (el.type === 'file' && el.files[0]) { 198 setter(el.name, el.files[0]); 199 return; 200 } 201 202 if (el.type !== 'radio') { 203 if (value === "" && el.required === false) { 204 // Don't include empty optional values in the request 205 return; 206 } 207 setter(el.name, value); 208 return; 209 } 210 211 if (el.checked) { 212 value = (value === 'false') ? false : true; 213 setter(el.name, value); 214 } 215 }); 216 217 const query = {}; 218 const queryParameters = form.querySelectorAll('input[data-component=query]'); 219 queryParameters.forEach(el => { 220 if (el.type !== 'radio' || (el.type === 'radio' && el.checked)) { 221 if (el.value === '') { 222 // Don't include empty values in the request 223 return; 224 } 225 226 _.set(query, el.name, el.value); 227 } 228 }); 229 230 let path = form.dataset.path; 231 const urlParameters = form.querySelectorAll('input[data-component=url]'); 232 urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value))); 233 234 const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]')) 235 .map(el => [el.name, el.value])); 236 237 // When using FormData, the browser sets the correct content-type + boundary 238 let method = form.dataset.method; 239 if (body instanceof FormData) { 240 delete headers['Content-Type']; 241 242 // When using FormData with PUT or PATCH, use method spoofing so PHP can access the post body 243 if (['PUT', 'PATCH'].includes(form.dataset.method)) { 244 method = 'POST'; 245 setter('_method', form.dataset.method); 246 } 247 } 248 249 let preflightPromise = Promise.resolve(); 250 if (window.useCsrf && window.csrfUrl) { 251 preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => { 252 headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN'); 253 }); 254 } 255 256 return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId)) 257 .then(([responseStatus, statusText, responseContent, responseHeaders]) => { 258 handleResponse(endpointId, responseContent, responseStatus, responseHeaders) 259 }) 260 .catch(err => { 261 if (err.name === "AbortError") { 262 console.log("Request cancelled"); 263 return; 264 } 265 console.log("Error while making request: ", err); 266 handleError(endpointId, err); 267 }) 268 .finally(() => { 269 executeBtn.disabled = false; 270 executeBtn.textContent = executeBtn.dataset.initialText; 271 }); 272 }