"use strict"; var fakeXhr = require("../fake-xhr"); var push = [].push; var log = require("./log"); var configureLogError = require("../configure-logger"); var pathToRegexp = require("path-to-regexp"); var supportsArrayBuffer = typeof ArrayBuffer !== "undefined"; function responseArray(handler) { var response = handler; if (Object.prototype.toString.call(handler) !== "[object Array]") { response = [200, {}, handler]; } if (typeof response[2] !== "string") { if (!supportsArrayBuffer) { throw new TypeError( `Fake server response body should be a string, but was ${typeof response[2]}` ); } else if (!(response[2] instanceof ArrayBuffer)) { throw new TypeError( `Fake server response body should be a string or ArrayBuffer, but was ${typeof response[2]}` ); } } return response; } function getDefaultWindowLocation() { var winloc = { hostname: "localhost", port: process.env.PORT || 80, protocol: "http:" }; winloc.host = winloc.hostname + (String(winloc.port) === "80" ? "" : `:${winloc.port}`); return winloc; } function getWindowLocation() { if (typeof window === "undefined") { // Fallback return getDefaultWindowLocation(); } if (typeof window.location !== "undefined") { // Browsers place location on window return window.location; } if ( typeof window.window !== "undefined" && typeof window.window.location !== "undefined" ) { // React Native on Android places location on window.window return window.window.location; } return getDefaultWindowLocation(); } function matchOne(response, reqMethod, reqUrl) { var rmeth = response.method; var matchMethod = !rmeth || rmeth.toLowerCase() === reqMethod.toLowerCase(); var url = response.url; var matchUrl = !url || url === reqUrl || (typeof url.test === "function" && url.test(reqUrl)) || (typeof url === "function" && url(reqUrl) === true); return matchMethod && matchUrl; } function match(response, request) { var wloc = getWindowLocation(); var rCurrLoc = new RegExp(`^${wloc.protocol}//${wloc.host}/`); var requestUrl = request.url; if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { requestUrl = requestUrl.replace(rCurrLoc, "/"); } if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { if (typeof response.response === "function") { var ru = response.url; var args = [request].concat( ru && typeof ru.exec === "function" ? ru.exec(requestUrl).slice(1) : [] ); return response.response.apply(response, args); } return true; } return false; } function incrementRequestCount() { var count = ++this.requestCount; this.requested = true; this.requestedOnce = count === 1; this.requestedTwice = count === 2; this.requestedThrice = count === 3; this.firstRequest = this.getRequest(0); this.secondRequest = this.getRequest(1); this.thirdRequest = this.getRequest(2); this.lastRequest = this.getRequest(count - 1); } var fakeServer = { create: function(config) { var server = Object.create(this); server.configure(config); this.xhr = fakeXhr.useFakeXMLHttpRequest(); server.requests = []; server.requestCount = 0; server.queue = []; server.responses = []; this.xhr.onCreate = function(xhrObj) { xhrObj.unsafeHeadersEnabled = function() { return !(server.unsafeHeadersEnabled === false); }; server.addRequest(xhrObj); }; return server; }, configure: function(config) { var self = this; var allowlist = { autoRespond: true, autoRespondAfter: true, respondImmediately: true, fakeHTTPMethods: true, logger: true, unsafeHeadersEnabled: true }; // eslint-disable-next-line no-param-reassign config = config || {}; Object.keys(config).forEach(function(setting) { if (setting in allowlist) { self[setting] = config[setting]; } }); self.logError = configureLogError(config); }, addRequest: function addRequest(xhrObj) { var server = this; push.call(this.requests, xhrObj); incrementRequestCount.call(this); xhrObj.onSend = function() { server.handleRequest(this); if (server.respondImmediately) { server.respond(); } else if (server.autoRespond && !server.responding) { setTimeout(function() { server.responding = false; server.respond(); }, server.autoRespondAfter || 10); server.responding = true; } }; }, getHTTPMethod: function getHTTPMethod(request) { if (this.fakeHTTPMethods && /post/i.test(request.method)) { var matches = (request.requestBody || "").match( /_method=([^\b;]+)/ ); return matches ? matches[1] : request.method; } return request.method; }, handleRequest: function handleRequest(xhr) { if (xhr.async) { push.call(this.queue, xhr); } else { this.processRequest(xhr); } }, logger: function() { // no-op; override via configure() }, logError: configureLogError({}), log: log, respondWith: function respondWith(method, url, body) { if (arguments.length === 1 && typeof method !== "function") { this.response = responseArray(method); return; } if (arguments.length === 1) { // eslint-disable-next-line no-param-reassign body = method; // eslint-disable-next-line no-param-reassign url = method = null; } if (arguments.length === 2) { // eslint-disable-next-line no-param-reassign body = url; // eslint-disable-next-line no-param-reassign url = method; // eslint-disable-next-line no-param-reassign method = null; } // Escape port number to prevent "named" parameters in 'path-to-regexp' module if (typeof url === "string" && url !== "" && /:[0-9]+\//.test(url)) { var m = url.match(/^(https?:\/\/.*?):([0-9]+\/.*)$/); // eslint-disable-next-line no-param-reassign url = `${m[1]}\\:${m[2]}`; } push.call(this.responses, { method: method, url: typeof url === "string" && url !== "" ? pathToRegexp(url) : url, response: typeof body === "function" ? body : responseArray(body) }); }, respond: function respond() { if (arguments.length > 0) { this.respondWith.apply(this, arguments); } var queue = this.queue || []; var requests = queue.splice(0, queue.length); var self = this; requests.forEach(function(request) { self.processRequest(request); }); }, respondAll: function respondAll() { if (this.respondImmediately) { return; } this.queue = this.requests.slice(0); var request; while ((request = this.queue.shift())) { this.processRequest(request); } }, processRequest: function processRequest(request) { try { if (request.aborted) { return; } var response = this.response || [404, {}, ""]; if (this.responses) { for (var l = this.responses.length, i = l - 1; i >= 0; i--) { if (match.call(this, this.responses[i], request)) { response = this.responses[i].response; break; } } } if (request.readyState !== 4) { this.log(response, request); request.respond(response[0], response[1], response[2]); } } catch (e) { this.logError("Fake server request processing", e); } }, restore: function restore() { return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); }, getRequest: function getRequest(index) { return this.requests[index] || null; }, reset: function reset() { this.resetBehavior(); this.resetHistory(); }, resetBehavior: function resetBehavior() { this.responses.length = this.queue.length = 0; }, resetHistory: function resetHistory() { this.requests.length = this.requestCount = 0; this.requestedOnce = this.requestedTwice = this.requestedThrice = this.requested = false; this.firstRequest = this.secondRequest = this.thirdRequest = this.lastRequest = null; } }; module.exports = fakeServer;