'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** * XHR based logger */ var _logger = require('../utils/logger'); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var XhrLoader = function () { function XhrLoader(config) { _classCallCheck(this, XhrLoader); if (config && config.xhrSetup) { this.xhrSetup = config.xhrSetup; } } _createClass(XhrLoader, [{ key: 'destroy', value: function destroy() { this.abort(); this.loader = null; } }, { key: 'abort', value: function abort() { var loader = this.loader; if (loader && loader.readyState !== 4) { this.stats.aborted = true; loader.abort(); } window.clearTimeout(this.requestTimeout); this.requestTimeout = null; window.clearTimeout(this.retryTimeout); this.retryTimeout = null; } }, { key: 'load', value: function load(context, config, callbacks) { this.context = context; this.config = config; this.callbacks = callbacks; this.stats = { trequest: performance.now(), retry: 0 }; this.retryDelay = config.retryDelay; this.loadInternal(); } }, { key: 'loadInternal', value: function loadInternal() { var xhr, context = this.context; if (typeof XDomainRequest !== 'undefined') { xhr = this.loader = new XDomainRequest(); } else { xhr = this.loader = new XMLHttpRequest(); } var stats = this.stats; stats.tfirst = 0; stats.loaded = 0; var xhrSetup = this.xhrSetup; try { if (xhrSetup) { try { xhrSetup(xhr, context.url); } catch (e) { // fix xhrSetup: (xhr, url) => {xhr.setRequestHeader("Content-Language", "test");} // not working, as xhr.setRequestHeader expects xhr.readyState === OPEN xhr.open('GET', context.url, true); xhrSetup(xhr, context.url); } } if (!xhr.readyState) { xhr.open('GET', context.url, true); } } catch (e) { // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS this.callbacks.onError({ code: xhr.status, text: e.message }, context, xhr); return; } if (context.rangeEnd) { xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1)); } xhr.onreadystatechange = this.readystatechange.bind(this); xhr.onprogress = this.loadprogress.bind(this); xhr.responseType = context.responseType; // setup timeout before we perform request this.requestTimeout = window.setTimeout(this.loadtimeout.bind(this), this.config.timeout); xhr.send(); } }, { key: 'readystatechange', value: function readystatechange(event) { var xhr = event.currentTarget, readyState = xhr.readyState, stats = this.stats, context = this.context, config = this.config; // don't proceed if xhr has been aborted if (stats.aborted) { return; } // >= HEADERS_RECEIVED if (readyState >= 2) { // clear xhr timeout and rearm it if readyState less than 4 window.clearTimeout(this.requestTimeout); if (stats.tfirst === 0) { stats.tfirst = Math.max(performance.now(), stats.trequest); } if (readyState === 4) { var status = xhr.status; // http status between 200 to 299 are all successful if (status >= 200 && status < 300) { stats.tload = Math.max(stats.tfirst, performance.now()); var data = void 0, len = void 0; if (context.responseType === 'arraybuffer') { data = xhr.response; len = data.byteLength; } else { data = xhr.responseText; len = data.length; } stats.loaded = stats.total = len; var response = { url: xhr.responseURL, data: data }; this.callbacks.onSuccess(response, stats, context, xhr); } else { // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error if (stats.retry >= config.maxRetry || status >= 400 && status < 499) { _logger.logger.error(status + ' while loading ' + context.url); this.callbacks.onError({ code: status, text: xhr.statusText }, context, xhr); } else { // retry _logger.logger.warn(status + ' while loading ' + context.url + ', retrying in ' + this.retryDelay + '...'); // aborts and resets internal state this.destroy(); // schedule retry this.retryTimeout = window.setTimeout(this.loadInternal.bind(this), this.retryDelay); // set exponential backoff this.retryDelay = Math.min(2 * this.retryDelay, config.maxRetryDelay); stats.retry++; } } } else { // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet this.requestTimeout = window.setTimeout(this.loadtimeout.bind(this), config.timeout); } } } }, { key: 'loadtimeout', value: function loadtimeout() { _logger.logger.warn('timeout while loading ' + this.context.url); this.callbacks.onTimeout(this.stats, this.context, null); } }, { key: 'loadprogress', value: function loadprogress(event) { var xhr = event.currentTarget, stats = this.stats; stats.loaded = event.loaded; if (event.lengthComputable) { stats.total = event.total; } var onProgress = this.callbacks.onProgress; if (onProgress) { // third arg is to provide on progress data onProgress(stats, this.context, null, xhr); } } }]); return XhrLoader; }(); exports.default = XhrLoader;