UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

512 lines (415 loc) 14 kB
/* eslint-disable max-classes-per-file */ import { getWindow, getDocument } from 'ssr-window'; import { extend, serializeObject } from './utils.js'; const globals = {}; let jsonpRequests = 0; class RequestResponse { constructor(obj) { Object.assign(this, obj); } } class RequestError extends Error { constructor(obj) { super(); Object.assign(this, obj); } } const request = requestOptions => new Promise((resolve, reject) => { const window = getWindow(); const document = getDocument(); const globalsNoCallbacks = extend({}, globals); 'beforeCreate beforeOpen beforeSend error complete success statusCode'.split(' ').forEach(callbackName => { delete globalsNoCallbacks[callbackName]; }); const defaults = extend({ url: window.location.toString(), method: 'GET', data: false, async: true, cache: true, user: '', password: '', headers: {}, xhrFields: {}, statusCode: {}, processData: true, dataType: 'text', contentType: 'application/x-www-form-urlencoded', timeout: 0 }, globalsNoCallbacks); let proceedRequest; const options = extend({}, defaults, requestOptions); if (requestOptions.abortController) { options.abortController = requestOptions.abortController; } if (options.abortController && options.abortController.canceled) { reject(new RequestError({ options, status: 'canceled', message: 'canceled' })); return; } // Function to run XHR callbacks and events function fireCallback(callbackName) { /* Callbacks: beforeCreate (options), beforeOpen (xhr, options), beforeSend (xhr, options), error (xhr, status, message), complete (xhr, status), success (response, status, xhr), statusCode () */ let globalCallbackValue; let optionCallbackValue; for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { data[_key - 1] = arguments[_key]; } if (globals[callbackName]) { globalCallbackValue = globals[callbackName](...data); } if (options[callbackName]) { optionCallbackValue = options[callbackName](...data); } if (typeof globalCallbackValue !== 'boolean') globalCallbackValue = true; if (typeof optionCallbackValue !== 'boolean') optionCallbackValue = true; if (options.abortController && options.abortController.canceled && (callbackName === 'beforeCreate' || callbackName === 'beforeOpen' || callbackName === 'beforeSend')) { return false; } return globalCallbackValue && optionCallbackValue; } // Before create callback proceedRequest = fireCallback('beforeCreate', options); if (proceedRequest === false) { reject(new RequestError({ options, status: 'canceled', message: 'canceled' })); return; } // For jQuery guys if (options.type) options.method = options.type; // Parameters Prefix let paramsPrefix = options.url.indexOf('?') >= 0 ? '&' : '?'; // UC method const method = options.method.toUpperCase(); // Data to modify GET URL if ((method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || method === 'DELETE') && options.data) { let stringData; if (typeof options.data === 'string') { // Should be key=value string if (options.data.indexOf('?') >= 0) stringData = options.data.split('?')[1];else stringData = options.data; } else { // Should be key=value object stringData = serializeObject(options.data); } if (stringData.length) { options.url += paramsPrefix + stringData; if (paramsPrefix === '?') paramsPrefix = '&'; } } // JSONP if (options.dataType === 'json' && options.url.indexOf('callback=') >= 0) { const callbackName = `f7jsonp_${Date.now() + (jsonpRequests += 1)}`; let abortTimeout; const callbackSplit = options.url.split('callback='); let requestUrl = `${callbackSplit[0]}callback=${callbackName}`; if (callbackSplit[1].indexOf('&') >= 0) { const addVars = callbackSplit[1].split('&').filter(el => el.indexOf('=') > 0).join('&'); if (addVars.length > 0) requestUrl += `&${addVars}`; } // Create script let script = document.createElement('script'); script.type = 'text/javascript'; script.onerror = function onerror() { clearTimeout(abortTimeout); fireCallback('error', null, 'scripterror', 'scripterror'); reject(new RequestError({ options, status: 'scripterror', message: 'scripterror' })); fireCallback('complete', null, 'scripterror'); }; script.src = requestUrl; // Handler window[callbackName] = function jsonpCallback(data) { clearTimeout(abortTimeout); fireCallback('success', data); script.parentNode.removeChild(script); script = null; delete window[callbackName]; resolve(new RequestResponse({ options, data })); }; document.querySelector('head').appendChild(script); if (options.timeout > 0) { abortTimeout = setTimeout(() => { script.parentNode.removeChild(script); script = null; fireCallback('error', null, 'timeout', 'timeout'); reject(new RequestError({ options, status: 'timeout', message: 'timeout' })); }, options.timeout); } return; } // Cache for GET/HEAD requests if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || method === 'DELETE') { if (options.cache === false) { options.url += `${paramsPrefix}_nocache${Date.now()}`; } } // Create XHR const xhr = new XMLHttpRequest(); if (options.abortController) { let aborted = false; options.abortController.onAbort = () => { if (aborted) return; aborted = true; xhr.abort(); reject(new RequestError({ options, xhr, status: 'canceled', message: 'canceled' })); }; } // Save Request URL xhr.requestUrl = options.url; xhr.requestParameters = options; // Before open callback proceedRequest = fireCallback('beforeOpen', xhr, options); if (proceedRequest === false) { reject(new RequestError({ options, xhr, status: 'canceled', message: 'canceled' })); return; } // Open XHR xhr.open(method, options.url, options.async, options.user, options.password); // Create POST Data let postData = null; if ((method === 'POST' || method === 'PUT' || method === 'PATCH') && options.data) { if (options.processData) { const postDataInstances = [ArrayBuffer, Blob, Document, FormData]; // Post Data if (postDataInstances.indexOf(options.data.constructor) >= 0) { postData = options.data; } else { // POST Headers const boundary = `---------------------------${Date.now().toString(16)}`; if (options.contentType === 'multipart/form-data') { xhr.setRequestHeader('Content-Type', `multipart/form-data; boundary=${boundary}`); } else { xhr.setRequestHeader('Content-Type', options.contentType); } postData = ''; let data = serializeObject(options.data); if (options.contentType === 'multipart/form-data') { data = data.split('&'); const newData = []; for (let i = 0; i < data.length; i += 1) { newData.push(`Content-Disposition: form-data; name="${data[i].split('=')[0]}"\r\n\r\n${data[i].split('=')[1]}\r\n`); } postData = `--${boundary}\r\n${newData.join(`--${boundary}\r\n`)}--${boundary}--\r\n`; } else if (options.contentType === 'application/json') { postData = JSON.stringify(options.data); } else { postData = data; } } } else { postData = options.data; xhr.setRequestHeader('Content-Type', options.contentType); } } if (options.dataType === 'json' && (!options.headers || !options.headers.Accept)) { xhr.setRequestHeader('Accept', 'application/json'); } // Additional headers if (options.headers) { Object.keys(options.headers).forEach(headerName => { if (typeof options.headers[headerName] === 'undefined') return; xhr.setRequestHeader(headerName, options.headers[headerName]); }); } // Check for crossDomain if (typeof options.crossDomain === 'undefined') { options.crossDomain = // eslint-disable-next-line /^([\w-]+:)?\/\/([^\/]+)/.test(options.url) && RegExp.$2 !== window.location.host; } if (!options.crossDomain) { xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); } if (options.xhrFields) { extend(xhr, options.xhrFields); } // Handle XHR xhr.onload = function onload() { if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 0) { let responseData; if (options.dataType === 'json') { let parseError; try { responseData = JSON.parse(xhr.responseText); } catch (err) { parseError = true; } if (!parseError) { fireCallback('success', responseData, xhr.status, xhr); resolve(new RequestResponse({ options, data: responseData, status: xhr.status, xhr })); } else { fireCallback('error', xhr, 'parseerror', 'parseerror'); reject(new RequestError({ options, xhr, status: 'parseerror', message: 'parseerror' })); } } else { responseData = xhr.responseType === 'text' || xhr.responseType === '' ? xhr.responseText : xhr.response; fireCallback('success', responseData, xhr.status, xhr); resolve(new RequestResponse({ options, data: responseData, status: xhr.status, xhr })); } } else { fireCallback('error', xhr, xhr.status, xhr.statusText); reject(new RequestError({ options, xhr, status: xhr.status, message: xhr.statusText })); } if (options.statusCode) { if (globals.statusCode && globals.statusCode[xhr.status]) globals.statusCode[xhr.status](xhr); if (options.statusCode[xhr.status]) options.statusCode[xhr.status](xhr); } fireCallback('complete', xhr, xhr.status); }; xhr.onerror = function onerror() { fireCallback('error', xhr, xhr.status, xhr.status); reject(new RequestError({ options, xhr, status: xhr.status, message: xhr.statusText })); fireCallback('complete', xhr, 'error'); }; // Timeout if (options.timeout > 0) { xhr.timeout = options.timeout; xhr.ontimeout = () => { fireCallback('error', xhr, 'timeout', 'timeout'); reject(new RequestError({ options, xhr, status: 'timeout', message: 'timeout' })); fireCallback('complete', xhr, 'timeout'); }; } // Ajax start callback proceedRequest = fireCallback('beforeSend', xhr, options); if (proceedRequest === false) { reject(new RequestError({ options, xhr, status: 'canceled', message: 'canceled' })); return; } // Send XHR xhr.send(postData); }); function requestShortcut(method) { let [url, data, success, error, dataType] = []; for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } if (typeof args[1] === 'function') { [url, success, error, dataType] = args; } else { [url, data, success, error, dataType] = args; } [success, error].forEach(callback => { if (typeof callback === 'string') { dataType = callback; if (callback === success) success = undefined;else error = undefined; } }); dataType = dataType || (method === 'json' || method === 'postJSON' ? 'json' : undefined); const requestOptions = { url, method: method === 'post' || method === 'postJSON' ? 'POST' : 'GET', data, success, error, dataType }; if (method === 'postJSON') { extend(requestOptions, { contentType: 'application/json', processData: false, crossDomain: true, data: typeof data === 'string' ? data : JSON.stringify(data) }); } return request(requestOptions); } Object.assign(request, { get: function () { for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } return requestShortcut('get', ...args); }, post: function () { for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { args[_key4] = arguments[_key4]; } return requestShortcut('post', ...args); }, json: function () { for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { args[_key5] = arguments[_key5]; } return requestShortcut('json', ...args); }, getJSON: function () { for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { args[_key6] = arguments[_key6]; } return requestShortcut('json', ...args); }, postJSON: function () { for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { args[_key7] = arguments[_key7]; } return requestShortcut('postJSON', ...args); } }); request.abortController = () => { const contoller = { canceled: false, onAbort: null, abort() { contoller.canceled = true; if (contoller.onAbort) contoller.onAbort(); } }; return contoller; }; request.setup = function setup(options) { if (options.type && !options.method) { extend(options, { method: options.type }); } extend(globals, options); }; export default request;