"use strict" var buildQueryString = require("../querystring/build") module.exports = function($window, Promise) { var callbackCount = 0 var oncompletion function makeRequest(factory) { return function(url, args) { if (typeof url !== "string") { args = url; url = url.url } else if (args == null) args = {} var promise = new Promise(function(resolve, reject) { factory(url, args, function (data) { if (typeof args.type === "function") { if (Array.isArray(data)) { for (var i = 0; i < data.length; i++) { data[i] = new args.type(data[i]) } } else data = new args.type(data) } resolve(data) }, reject) }) if (args.background === true) return promise var count = 0 function complete() { if (--count === 0 && typeof oncompletion === "function") oncompletion() } return wrap(promise) function wrap(promise) { var then = promise.then promise.then = function() { count++ var next = then.apply(promise, arguments) next.then(complete, function(e) { complete() if (count === 0) throw e }) return wrap(next) } return promise } } } function hasHeader(args, name) { for (var key in args.headers) { if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true } return false } function interpolate(url, data, assemble) { if (data == null) return url url = url.replace(/:([^\/]+)/gi, function (m, key) { return data[key] != null ? data[key] : m }) if (assemble && data != null) { var querystring = buildQueryString(data) if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring } return url } return { request: makeRequest(function(url, args, resolve, reject) { var method = args.method != null ? args.method.toUpperCase() : "GET" var useBody = method !== "GET" && method !== "TRACE" && (typeof args.useBody !== "boolean" || args.useBody) var data = args.data var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) if (useBody) { if (typeof args.serialize === "function") data = args.serialize(data) else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) } var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort xhr.abort = function abort() { aborted = true _abort.call(xhr) } xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) if (assumeJSON && useBody && !hasHeader(args, /^content-type$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials if (args.timeout) xhr.timeout = args.timeout if (args.responseType) xhr.responseType = args.responseType for (var key in args.headers) { if ({}.hasOwnProperty.call(args.headers, key)) { xhr.setRequestHeader(key, args.headers[key]) } } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { // Don't throw errors on xhr.abort(). if(aborted) return if (xhr.readyState === 4) { try { var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) var response = xhr.responseText if (typeof args.extract === "function") { response = args.extract(xhr, args) success = true } else if (typeof args.deserialize === "function") { response = args.deserialize(response) } else { try {response = response ? JSON.parse(response) : null} catch (e) {throw new Error("Invalid JSON: " + response)} } if (success) resolve(response) else { var error = new Error(xhr.responseText) error.code = xhr.status error.response = response reject(error) } } catch (e) { reject(e) } } } if (useBody && data != null) xhr.send(data) else xhr.send() }), jsonp: makeRequest(function(url, args, resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { script.parentNode.removeChild(script) resolve(data) delete $window[callbackName] } script.onerror = function() { script.parentNode.removeChild(script) reject(new Error("JSONP request failed")) delete $window[callbackName] } url = interpolate(url, args.data, true) script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + encodeURIComponent(args.callbackKey || "callback") + "=" + encodeURIComponent(callbackName) $window.document.documentElement.appendChild(script) }), setCompletionCallback: function(callback) { oncompletion = callback }, } }