From 107f33b58e344b539b562d8d731cab8830338c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20I=2E=20Silva?= Date: Thu, 11 Apr 2019 19:29:04 -0700 Subject: [PATCH 1/2] refactor: extract create-passthrough --- src/create-passthrough.ts | 97 +++++++++++++++++++++++++++++++++++++++ src/index.ts | 92 ++----------------------------------- 2 files changed, 101 insertions(+), 88 deletions(-) create mode 100644 src/create-passthrough.ts diff --git a/src/create-passthrough.ts b/src/create-passthrough.ts new file mode 100644 index 0000000..820dec8 --- /dev/null +++ b/src/create-passthrough.ts @@ -0,0 +1,97 @@ +export function createPassthrough(fakeXHR, nativeXMLHttpRequest) { + // event types to handle on the xhr + var evts = ['error', 'timeout', 'abort', 'readystatechange']; + + // event types to handle on the xhr.upload + var uploadEvents = []; + + // properties to copy from the native xhr to fake xhr + var lifecycleProps = [ + 'readyState', + 'responseText', + 'response', + 'responseXML', + 'responseURL', + 'status', + 'statusText', + ]; + + var xhr = (fakeXHR._passthroughRequest = new nativeXMLHttpRequest()); + xhr.open( + fakeXHR.method, + fakeXHR.url, + fakeXHR.async, + fakeXHR.username, + fakeXHR.password + ); + + if (fakeXHR.responseType === 'arraybuffer') { + lifecycleProps = ['readyState', 'response', 'status', 'statusText']; + xhr.responseType = fakeXHR.responseType; + } + + // use onload if the browser supports it + if ('onload' in xhr) { + evts.push('load'); + } + + // add progress event for async calls + // avoid using progress events for sync calls, they will hang https://bugs.webkit.org/show_bug.cgi?id=40996. + if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') { + evts.push('progress'); + uploadEvents.push('progress'); + } + + // update `propertyNames` properties from `fromXHR` to `toXHR` + function copyLifecycleProperties(propertyNames, fromXHR, toXHR) { + for (var i = 0; i < propertyNames.length; i++) { + var prop = propertyNames[i]; + if (prop in fromXHR) { + toXHR[prop] = fromXHR[prop]; + } + } + } + + // fire fake event on `eventable` + function dispatchEvent(eventable, eventType, event) { + eventable.dispatchEvent(event); + if (eventable['on' + eventType]) { + eventable['on' + eventType](event); + } + } + + // set the on- handler on the native xhr for the given eventType + function createHandler(eventType) { + xhr['on' + eventType] = function (event) { + copyLifecycleProperties(lifecycleProps, xhr, fakeXHR); + dispatchEvent(fakeXHR, eventType, event); + }; + } + + // set the on- handler on the native xhr's `upload` property for + // the given eventType + function createUploadHandler(eventType) { + if (xhr.upload) { + xhr.upload['on' + eventType] = function (event) { + dispatchEvent(fakeXHR.upload, eventType, event); + }; + } + } + + var i; + for (i = 0; i < evts.length; i++) { + createHandler(evts[i]); + } + for (i = 0; i < uploadEvents.length; i++) { + createUploadHandler(uploadEvents[i]); + } + + if (fakeXHR.async) { + xhr.timeout = fakeXHR.timeout; + xhr.withCredentials = fakeXHR.withCredentials; + } + for (var h in fakeXHR.requestHeaders) { + xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]); + } + return xhr; +} diff --git a/src/index.ts b/src/index.ts index a2848c0..f0ba8f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import * as FakeFetch from 'whatwg-fetch'; import parseURL from './parse-url'; import Registry from './registry'; import Hosts from './hosts'; +import {createPassthrough} from './create-passthrough'; function Pretender(/* routeMap1, routeMap2, ..., options*/) { this.hosts = new Hosts(); @@ -68,107 +69,22 @@ function interceptor(ctx) { FakeXMLHttpRequest.prototype.send.apply(this, arguments); if (ctx.pretender.checkPassthrough(this)) { - this.passthrough(); + this.passthrough(ctx.pretender._nativeXMLHttpRequest); } else { ctx.pretender.handleRequest(this); } }; - FakeRequest.prototype.passthrough = function passthrough() { + FakeRequest.prototype.passthrough = function passthrough(nativeXMLHttpRequest) { if (!this.sendArguments) { throw new Error('You attempted to passthrough a FakeRequest that was never sent. ' + 'Call `.send()` on the original request first'); } - let xhr = createPassthrough(this); + var xhr = createPassthrough(this, nativeXMLHttpRequest); xhr.send.apply(xhr, this.sendArguments); return xhr; }; - - function createPassthrough(fakeXHR) { - // event types to handle on the xhr - let evts = ['error', 'timeout', 'abort', 'readystatechange']; - - // event types to handle on the xhr.upload - let uploadEvents = []; - - // properties to copy from the native xhr to fake xhr - let lifecycleProps = ['readyState', 'responseText', 'response', 'responseXML', 'responseURL', 'status', 'statusText']; - - let xhr = fakeXHR._passthroughRequest = new ctx.pretender._nativeXMLHttpRequest(); - xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); - - if (fakeXHR.responseType === 'arraybuffer') { - lifecycleProps = ['readyState', 'response', 'status', 'statusText']; - xhr.responseType = fakeXHR.responseType; - } - - // use onload if the browser supports it - if ('onload' in xhr) { - evts.push('load'); - } - - // add progress event for async calls - // avoid using progress events for sync calls, they will hang https://bugs.webkit.org/show_bug.cgi?id=40996. - if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') { - evts.push('progress'); - uploadEvents.push('progress'); - } - - // update `propertyNames` properties from `fromXHR` to `toXHR` - function copyLifecycleProperties(propertyNames, fromXHR, toXHR) { - for (let i = 0; i < propertyNames.length; i++) { - let prop = propertyNames[i]; - if (prop in fromXHR) { - toXHR[prop] = fromXHR[prop]; - } - } - } - - // fire fake event on `eventable` - function dispatchEvent(eventable, eventType, event) { - eventable.dispatchEvent(event); - if (eventable['on' + eventType]) { - eventable['on' + eventType](event); - } - } - - // set the on- handler on the native xhr for the given eventType - function createHandler(eventType) { - xhr['on' + eventType] = function(event) { - copyLifecycleProperties(lifecycleProps, xhr, fakeXHR); - dispatchEvent(fakeXHR, eventType, event); - }; - } - - // set the on- handler on the native xhr's `upload` property for - // the given eventType - function createUploadHandler(eventType) { - if (xhr.upload) { - xhr.upload['on' + eventType] = function(event) { - dispatchEvent(fakeXHR.upload, eventType, event); - }; - } - } - - let i; - for (i = 0; i < evts.length; i++) { - createHandler(evts[i]); - } - for (i = 0; i < uploadEvents.length; i++) { - createUploadHandler(uploadEvents[i]); - } - - if (fakeXHR.async) { - xhr.timeout = fakeXHR.timeout; - xhr.withCredentials = fakeXHR.withCredentials; - } - for (let h in fakeXHR.requestHeaders) { - xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]); - } - return xhr; - } - FakeRequest.prototype._passthroughCheck = function(method, args) { if (this._passthroughRequest) { return this._passthroughRequest[method].apply(this._passthroughRequest, args); From f73f7bd212ae917397b4ce5d2c1c663476c3901d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20I=2E=20Silva?= Date: Thu, 11 Apr 2019 19:55:17 -0700 Subject: [PATCH 2/2] refactor: extract interceptor --- src/index.ts | 68 +--------------------------------- src/interceptor.ts | 67 +++++++++++++++++++++++++++++++++ test/force_passthrough_test.js | 2 +- 3 files changed, 69 insertions(+), 68 deletions(-) create mode 100644 src/interceptor.ts diff --git a/src/index.ts b/src/index.ts index f0ba8f2..42ef1b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ -import FakeXMLHttpRequest from 'fake-xml-http-request'; import * as FakeFetch from 'whatwg-fetch'; import parseURL from './parse-url'; import Registry from './registry'; import Hosts from './hosts'; -import {createPassthrough} from './create-passthrough'; +import { interceptor } from './interceptor'; function Pretender(/* routeMap1, routeMap2, ..., options*/) { this.hosts = new Hosts(); @@ -49,71 +48,6 @@ function Pretender(/* routeMap1, routeMap2, ..., options*/) { } } -function interceptor(ctx) { - function FakeRequest() { - // super() - FakeXMLHttpRequest.call(this); - } - FakeRequest.prototype = Object.create(FakeXMLHttpRequest.prototype); - FakeRequest.prototype.constructor = FakeRequest; - - // extend - FakeRequest.prototype.send = function send() { - this.sendArguments = arguments; - if (!ctx.pretender.running) { - throw new Error('You shut down a Pretender instance while there was a pending request. ' + - 'That request just tried to complete. Check to see if you accidentally shut down ' + - 'a pretender earlier than you intended to'); - } - - FakeXMLHttpRequest.prototype.send.apply(this, arguments); - - if (ctx.pretender.checkPassthrough(this)) { - this.passthrough(ctx.pretender._nativeXMLHttpRequest); - } else { - ctx.pretender.handleRequest(this); - } - }; - - FakeRequest.prototype.passthrough = function passthrough(nativeXMLHttpRequest) { - if (!this.sendArguments) { - throw new Error('You attempted to passthrough a FakeRequest that was never sent. ' + - 'Call `.send()` on the original request first'); - } - var xhr = createPassthrough(this, nativeXMLHttpRequest); - xhr.send.apply(xhr, this.sendArguments); - return xhr; - }; - - FakeRequest.prototype._passthroughCheck = function(method, args) { - if (this._passthroughRequest) { - return this._passthroughRequest[method].apply(this._passthroughRequest, args); - } - return FakeXMLHttpRequest.prototype[method].apply(this, args); - }; - - FakeRequest.prototype.abort = function abort() { - return this._passthroughCheck('abort', arguments); - }; - - FakeRequest.prototype.getResponseHeader = function getResponseHeader() { - return this._passthroughCheck('getResponseHeader', arguments); - }; - - FakeRequest.prototype.getAllResponseHeaders = function getAllResponseHeaders() { - return this._passthroughCheck('getAllResponseHeaders', arguments); - }; - - if (ctx.pretender._nativeXMLHttpRequest.prototype._passthroughCheck) { - // eslint-disable-next-line no-console - console.warn('You created a second Pretender instance while there was already one running. ' + - 'Running two Pretender servers at once will lead to unexpected results and will ' + - 'be removed entirely in a future major version.' + - 'Please call .shutdown() on your instances when you no longer need them to respond.'); - } - return FakeRequest; -} - function verbify(verb) { return function(path, handler, async) { return this.register(verb, path, handler, async); diff --git a/src/interceptor.ts b/src/interceptor.ts new file mode 100644 index 0000000..7e73cee --- /dev/null +++ b/src/interceptor.ts @@ -0,0 +1,67 @@ +import FakeXMLHttpRequest from 'fake-xml-http-request'; +import { createPassthrough } from './create-passthrough'; + +export function interceptor(ctx) { + function FakeRequest() { + // super() + FakeXMLHttpRequest.call(this); + } + FakeRequest.prototype = Object.create(FakeXMLHttpRequest.prototype); + FakeRequest.prototype.constructor = FakeRequest; + + // extend + FakeRequest.prototype.send = function send() { + this.sendArguments = arguments; + if (!ctx.pretender.running) { + throw new Error('You shut down a Pretender instance while there was a pending request. ' + + 'That request just tried to complete. Check to see if you accidentally shut down ' + + 'a pretender earlier than you intended to'); + } + + FakeXMLHttpRequest.prototype.send.apply(this, arguments); + + if (ctx.pretender.checkPassthrough(this)) { + this.passthrough(); + } else { + ctx.pretender.handleRequest(this); + } + }; + + FakeRequest.prototype.passthrough = function passthrough() { + if (!this.sendArguments) { + throw new Error('You attempted to passthrough a FakeRequest that was never sent. ' + + 'Call `.send()` on the original request first'); + } + var xhr = createPassthrough(this, ctx.pretender._nativeXMLHttpRequest); + xhr.send.apply(xhr, this.sendArguments); + return xhr; + }; + + FakeRequest.prototype._passthroughCheck = function(method, args) { + if (this._passthroughRequest) { + return this._passthroughRequest[method].apply(this._passthroughRequest, args); + } + return FakeXMLHttpRequest.prototype[method].apply(this, args); + }; + + FakeRequest.prototype.abort = function abort() { + return this._passthroughCheck('abort', arguments); + }; + + FakeRequest.prototype.getResponseHeader = function getResponseHeader() { + return this._passthroughCheck('getResponseHeader', arguments); + }; + + FakeRequest.prototype.getAllResponseHeaders = function getAllResponseHeaders() { + return this._passthroughCheck('getAllResponseHeaders', arguments); + }; + + if (ctx.pretender._nativeXMLHttpRequest.prototype._passthroughCheck) { + // eslint-disable-next-line no-console + console.warn('You created a second Pretender instance while there was already one running. ' + + 'Running two Pretender servers at once will lead to unexpected results and will ' + + 'be removed entirely in a future major version.' + + 'Please call .shutdown() on your instances when you no longer need them to respond.'); + } + return FakeRequest; +} diff --git a/test/force_passthrough_test.js b/test/force_passthrough_test.js index 6245f92..a716e60 100644 --- a/test/force_passthrough_test.js +++ b/test/force_passthrough_test.js @@ -1,7 +1,7 @@ var describe = QUnit.module; var it = QUnit.test; -describe('passthrough requests', function(config) { +describe('force passthrough requests', function(config) { config.beforeEach(function() { this.pretender = new Pretender({ forcePassthrough: true }); });