Skip to content

Commit

Permalink
fix(adapter-puppeteer): Do not intercept CORS preflight requests (#90)
Browse files Browse the repository at this point in the history
* fix(adapter-puppeteer): Do not intercept CORS preflight requests

* fix(adapter-puppeteer): Use a query param instead of a header
  • Loading branch information
offirgolan authored and jasonmit committed Aug 11, 2018
1 parent 6962bf2 commit 53ad433
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 89 deletions.
100 changes: 37 additions & 63 deletions packages/@pollyjs/adapter-puppeteer/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Adapter from '@pollyjs/adapter';
import { URL } from '@pollyjs/utils';

const LISTENERS = Symbol();
const POLLY_REQUEST = Symbol();
const PASSTHROUGH_PROMISE = Symbol();
const PASSTHROUGH_PROMISES = Symbol();
const PASSTHROUGH_REQ_ID_HEADER = 'x-pollyjs-passthrough-request-id';
const PASSTHROUGH_REQ_ID_QP = 'pollyjs_passthrough_req_id';

export default class PuppeteerAdapter extends Adapter {
static get name() {
Expand Down Expand Up @@ -42,51 +43,41 @@ export default class PuppeteerAdapter extends Adapter {
this[LISTENERS].set(page, {
request: request => {
if (requestResourceTypes.includes(request.resourceType())) {
const url = request.url();
const method = request.method();
const headers = request.headers();

// A CORS preflight request is a CORS request that checks to see
// if the CORS protocol is understood.
const isPreFlightReq =
request.method() === 'OPTIONS' &&
method === 'OPTIONS' &&
!!headers['origin'] &&
!!headers['access-control-request-method'];

if (
isPreFlightReq &&
(headers['access-control-request-headers'] || '').includes(
PASSTHROUGH_REQ_ID_HEADER
)
) {
// If this is a preflight request and contains the polly passthrough
// header, then force allow the request.
request.respond({
status: 200,
headers: {
'Access-Control-Allow-Origin': headers['origin'],
'Access-Control-Allow-Method':
headers['access-control-request-method'],
'Access-Control-Allow-Headers':
headers['access-control-request-headers']
}
});
} else if (headers[PASSTHROUGH_REQ_ID_HEADER]) {
// Do not intercept requests with the Polly passthrough QP
if (url.includes(PASSTHROUGH_REQ_ID_QP)) {
const parsedUrl = new URL(url, true);

// If this is a polly passthrough request
// Get the associated promise object for the request id and set it
// on the request
request[PASSTHROUGH_PROMISE] = this[PASSTHROUGH_PROMISES].get(
headers[PASSTHROUGH_REQ_ID_HEADER]
parsedUrl.query[PASSTHROUGH_REQ_ID_QP]
);

// Delete the header to remove any pollyjs footprint
delete headers[PASSTHROUGH_REQ_ID_HEADER];
// Delete the query param to remove any pollyjs footprint
delete parsedUrl.query[PASSTHROUGH_REQ_ID_QP];

// Continue the request with the headers override
request.continue({ headers });
// Continue the request with the url override
request.continue({ url: parsedUrl.toString() });
} else if (isPreFlightReq) {
// Do not intercept preflight requests
request.continue();
} else {
this.handleRequest({
headers,
url: request.url(),
method: request.method(),
url,
method,
body: request.postData(),
requestArguments: [request]
});
Expand All @@ -95,32 +86,33 @@ export default class PuppeteerAdapter extends Adapter {
request.continue();
}
},
response: response => {
const request = response.request();
requestfinished: request => {
const response = request.response();

// Resolve the passthrough promise with the response if it exists
if (request[PASSTHROUGH_PROMISE]) {
request[PASSTHROUGH_PROMISE].resolve(response);
delete request[PASSTHROUGH_PROMISE];
}
},
requestfinished: request => {

// Resolve the deferred pollyRequest promise if it exists
if (request[POLLY_REQUEST]) {
request[POLLY_REQUEST].promise.resolve(request.response());
request[POLLY_REQUEST].promise.resolve(response);
delete request[POLLY_REQUEST];
}
},
requestfailed: request => {
const error = request.failure();

// Reject the passthrough promise with the error object if it exists
if (request[PASSTHROUGH_PROMISE]) {
request[PASSTHROUGH_PROMISE].reject(request.failure());
request[PASSTHROUGH_PROMISE].reject(error);
delete request[PASSTHROUGH_PROMISE];
}

// Reject the deferred pollyRequest promise with the error object if it exists
if (request[POLLY_REQUEST]) {
request[POLLY_REQUEST].promise.reject(request.failure());
request[POLLY_REQUEST].promise.reject(error);
delete request[POLLY_REQUEST];
}
},
Expand Down Expand Up @@ -191,47 +183,29 @@ export default class PuppeteerAdapter extends Adapter {

async passthroughRequest(pollyRequest) {
const { page } = this.options;
const { id, order, url, method, body } = pollyRequest;
const { id, order, url, method, headers, body } = pollyRequest;
const requestId = `${this.polly.recordingId}:${id}:${order}`;
const headers = {
...pollyRequest.headers,
[PASSTHROUGH_REQ_ID_HEADER]: requestId
};
const parsedUrl = new URL(url, true);

parsedUrl.query[PASSTHROUGH_REQ_ID_QP] = requestId;

try {
const response = await new Promise((resolve, reject) => {
this[PASSTHROUGH_PROMISES].set(requestId, { resolve, reject });

// This gets evaluated within the browser's context, meaning that
// this fetch call executes from within the browser.
page.evaluate((url, options) => fetch(url, options), url, {
method,
headers,
body
});
page.evaluate(
(url, options) => fetch(url, options),
parsedUrl.toString(),
{ method, headers, body }
);
});
let responseBody;

try {
responseBody = await response.text();
} catch (error) {
/*
Rethrow the resulting error is not the following:
Error: Protocol error (Network.getResponseBody): No data found for resource with given identifier
*/
if (
error &&
error.message &&
!error.message.includes('Protocol error (Network.getResponseBody)')
) {
throw error;
}
}

return pollyRequest.respond(
response.status(),
response.headers(),
responseBody
await response.text()
);
} catch (error) {
throw error;
Expand Down
3 changes: 1 addition & 2 deletions packages/@pollyjs/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@
"lodash-es": "^4.17.10",
"merge-options": "^1.0.1",
"route-recognizer": "^0.3.3",
"slugify": "^1.3.0",
"url-parse": "^1.4.1"
"slugify": "^1.3.0"
},
"devDependencies": {
"@pollyjs/adapter": "^1.1.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/@pollyjs/core/src/-private/request.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import md5 from 'blueimp-md5';
import URL from 'url-parse';
import stringify from 'fast-json-stable-stringify';
import PollyResponse from './response';
import NormalizeRequest from '../utils/normalize-request';
import parseUrl from '../utils/parse-url';
import serializeRequestBody from '../utils/serialize-request-body';
import DeferredPromise from '../utils/deferred-promise';
import isAbsoluteUrl from 'is-absolute-url';
import { assert, timestamp } from '@pollyjs/utils';
import { URL, assert, timestamp } from '@pollyjs/utils';
import HTTPBase from './http-base';

const { keys, freeze } = Object;
Expand Down
3 changes: 1 addition & 2 deletions packages/@pollyjs/core/src/server/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import RouteRecognizer from 'route-recognizer';
import URL from 'url-parse';
import Route from './route';
import Handler from './handler';
import RouteHandler from './route-handler';
import Middleware from './middleware';
import removeHostFromUrl from '../utils/remove-host-from-url';
import castArray from 'lodash-es/castArray';
import { assert, timeout, buildUrl } from '@pollyjs/utils';
import { URL, assert, timeout, buildUrl } from '@pollyjs/utils';

const HOST = Symbol();
const NAMESPACES = Symbol();
Expand Down
2 changes: 1 addition & 1 deletion packages/@pollyjs/core/src/utils/parse-url.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import URL from 'url-parse';
import isAbsoluteUrl from 'is-absolute-url';
import removeHostFromUrl from './remove-host-from-url';
import { URL } from '@pollyjs/utils';

/**
* Creates an exact representation of the passed url string with url-parse.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import URL from 'url-parse';
import removeHost from '../../../src/utils/remove-host-from-url';
import { URL } from '@pollyjs/utils';

describe('Unit | Utils | removeHostFromUrl', function() {
it('should exist', function() {
Expand Down
15 changes: 0 additions & 15 deletions packages/@pollyjs/core/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -832,10 +832,6 @@ pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"

querystringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755"

read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
Expand Down Expand Up @@ -867,10 +863,6 @@ require-uncached@^1.0.3:
caller-path "^0.1.0"
resolve-from "^1.0.0"

requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"

resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
Expand Down Expand Up @@ -1081,13 +1073,6 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"

url-parse@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.1.tgz#4dec9dad3dc8585f862fed461d2e19bbf623df30"
dependencies:
querystringify "^2.0.0"
requires-port "^1.0.0"

util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down
2 changes: 2 additions & 0 deletions packages/@pollyjs/utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export { default as buildUrl } from './utils/build-url';

export { default as XHR } from './utils/xhr';
export { default as Fetch } from './utils/fetch';

export { default as URL } from 'url-parse';
14 changes: 11 additions & 3 deletions tests/integration/adapter-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,21 @@ export default function adapterTests() {

it('should work with CORS requests', async function() {
const { server } = this.polly;
const apiUrl = 'https://aws.random.cat/meow';
const apiUrl = 'https://jsonplaceholder.typicode.com';

server.get(apiUrl).passthrough();
server.get(`${apiUrl}/*`).passthrough();
server.post(`${apiUrl}/*`).passthrough();

const res = await this.fetch(apiUrl);
let res = await this.fetch(`${apiUrl}/posts/1`);

expect(res.ok).to.be.true;
expect(await res.json()).to.be.an('object');

res = await this.fetch(`${apiUrl}/posts`, {
method: 'POST',
body: JSON.stringify({ foo: 'bar' })
});

expect(res.ok).to.be.true;
});
}

0 comments on commit 53ad433

Please sign in to comment.