const $x = (function() { const send = function(method, url, options, body) { if(options === undefined) options = {}; else if(typeof options !== 'object') throw 'options must be undefined or an object'; const xhr = new XMLHttpRequest; const requestHeaders = new Map; if('headers' in options && typeof options.headers === 'object') for(const name in options.headers) if(options.headers.hasOwnProperty(name)) requestHeaders.set(name.toLowerCase(), options.headers[name]); if(typeof options.download === 'function') { xhr.onloadstart = ev => options.download(ev); xhr.onprogress = ev => options.download(ev); xhr.onloadend = ev => options.download(ev); } if(typeof options.upload === 'function') { xhr.upload.onloadstart = ev => options.upload(ev); xhr.upload.onprogress = ev => options.upload(ev); xhr.upload.onloadend = ev => options.upload(ev); } if(options.authed) xhr.withCredentials = true; if(typeof options.timeout === 'number') xhr.timeout = options.timeout; if(typeof options.type === 'string') xhr.responseType = options.type; if(typeof options.abort === 'function') options.abort(() => xhr.abort()); if(typeof options.xhr === 'function') options.xhr(() => xhr); if(typeof body === 'object') { if(body instanceof URLSearchParams) { requestHeaders.set('content-type', 'application/x-www-form-urlencoded'); } else if(body instanceof FormData) { // content-type is implicitly set } else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) { if(!requestHeaders.has('content-type')) requestHeaders.set('content-type', 'application/octet-stream'); } else if(!requestHeaders.has('content-type')) { const bodyParts = []; for(const name in body) if(body.hasOwnProperty(name)) bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name])); body = bodyParts.join('&'); requestHeaders.set('content-type', 'application/x-www-form-urlencoded'); } } return new Promise((resolve, reject) => { let responseHeaders = undefined; xhr.onload = ev => resolve({ status: xhr.status, body: () => xhr.response, text: () => xhr.responseText, headers: () => { if(responseHeaders !== undefined) return responseHeaders; responseHeaders = new Map; const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/); for(const name in raw) if(raw.hasOwnProperty(name)) { const parts = raw[name].split(': '); responseHeaders.set(parts.shift(), parts.join(': ')); } return responseHeaders; }, xhr: xhr, ev: ev, }); xhr.onabort = ev => reject({ abort: true, xhr: xhr, ev: ev, }); xhr.onerror = ev => reject({ abort: false, xhr: xhr, ev: ev, }); xhr.open(method, url); for(const [name, value] of requestHeaders) xhr.setRequestHeader(name, value); xhr.send(body); }); }; return { send: send, get: (url, options, body) => send('GET', url, options, body), post: (url, options, body) => send('POST', url, options, body), delete: (url, options, body) => send('DELETE', url, options, body), patch: (url, options, body) => send('PATCH', url, options, body), put: (url, options, body) => send('PUT', url, options, body), }; })();