fetch-multipart-graphql
Advanced tools
Comparing version 1.0.5 to 1.0.6
@@ -6,67 +6,81 @@ 'use strict'; | ||
}); | ||
exports.parseMultipartHTTP = parseMultipartHTTP; | ||
// borrowed mostly from apollo-link | ||
function parseMultipartHTTP(plaintext) { | ||
var results = []; | ||
// Split plaintext using encapsulation boundary | ||
var boundary = '\r\n---\r\n'; | ||
var terminatingBoundary = '\r\n-----\r\n'; | ||
var parts = plaintext.split(boundary); | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var part = _step.value; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
// Split part into header and body | ||
if (part.length) { | ||
var partArr = part.split('\r\n\r\n'); | ||
// Read the Content-Length header, which must be included in the response | ||
var headers = partArr[0]; | ||
var headersArr = headers.split('\r\n'); | ||
var contentLengthHeader = headersArr.find(function (headerLine) { | ||
return headerLine.toLowerCase().indexOf('content-length:') >= 0; | ||
}); | ||
if (contentLengthHeader === undefined) { | ||
return null; | ||
} | ||
var contentLengthArr = contentLengthHeader.split(':'); | ||
var contentLength = void 0; | ||
if (contentLengthArr.length === 2 && !isNaN(parseInt(contentLengthArr[1]))) { | ||
contentLength = parseInt(contentLengthArr[1]); | ||
} else { | ||
return null; | ||
} | ||
var body = partArr[1]; | ||
if (body && body.length) { | ||
// Strip out the terminating boundary | ||
body = body.replace(terminatingBoundary, ''); | ||
// Check that length of body matches the Content-Length | ||
if (body.length !== contentLength) { | ||
return null; | ||
} | ||
results.push(JSON.parse(body)); | ||
} else { | ||
return null; | ||
} | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
exports.parseMultipartHttp = parseMultipartHttp; | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
var boundary = '\r\n---\r\n'; | ||
var terminatingBoundary = '\r\n-----\r\n'; | ||
function splitWithRest(string, delim) { | ||
var index = string.indexOf(delim); | ||
if (index < 0) { | ||
return [string]; | ||
} | ||
return [string.substring(0, index), string.substring(index + delim.length)]; | ||
} | ||
return results; | ||
function parseMultipartHttp(buffer) { | ||
var previousParts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
var _splitWithRest = splitWithRest(buffer, boundary), | ||
_splitWithRest2 = _slicedToArray(_splitWithRest, 2), | ||
rest = _splitWithRest2[1]; | ||
if (!(rest && rest.length)) { | ||
// we did not finish receiving the initial boundary | ||
return { | ||
newBuffer: buffer, | ||
parts: previousParts | ||
}; | ||
} | ||
var parts = splitWithRest(rest, '\r\n\r\n'); | ||
var headers = parts[0]; | ||
rest = parts[1]; | ||
if (!(rest && rest.length)) { | ||
// we did not finish receiving the headers | ||
return { | ||
newBuffer: buffer, | ||
parts: previousParts | ||
}; | ||
} | ||
var headersArr = headers.split('\r\n'); | ||
var contentLengthHeader = headersArr.find(function (headerLine) { | ||
return headerLine.toLowerCase().indexOf('content-length:') >= 0; | ||
}); | ||
if (contentLengthHeader === undefined) { | ||
throw new Error('Invalid MultiPart Response, no content-length header'); | ||
} | ||
var contentLengthArr = contentLengthHeader.split(':'); | ||
var contentLength = void 0; | ||
if (contentLengthArr.length === 2 && !isNaN(parseInt(contentLengthArr[1]))) { | ||
contentLength = parseInt(contentLengthArr[1]); | ||
} else { | ||
throw new Error('Invalid MultiPart Response, could not parse content-length'); | ||
} | ||
// Strip out the terminating boundary | ||
rest = rest.replace(terminatingBoundary, ''); | ||
if (rest.length < contentLength) { | ||
// still waiting for more body to be sent; | ||
return { | ||
newBuffer: buffer, | ||
parts: previousParts | ||
}; | ||
} | ||
var body = rest.substring(0, contentLength); | ||
var nextBuffer = rest.substring(contentLength); | ||
var part = JSON.parse(body); | ||
var newParts = [].concat(_toConsumableArray(previousParts), [part]); | ||
if (nextBuffer.length) { | ||
return parseMultipartHttp(nextBuffer, newParts); | ||
} | ||
return { parts: newParts, newBuffer: '' }; | ||
} |
@@ -8,3 +8,3 @@ 'use strict'; | ||
var _parseMultipartHTTP = require('./parseMultipartHTTP'); | ||
var _parseMultipartHttp2 = require('./parseMultipartHttp'); | ||
@@ -43,50 +43,49 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
this.previousResponse = null; | ||
this.processedChunks = 0; | ||
this.chunkBuffer = ''; | ||
this.processedChunks = 0; | ||
} | ||
PatchResolver.prototype.handleChunk = function (data) { | ||
var results = (0, _parseMultipartHTTP.parseMultipartHTTP)(this.chunkBuffer + data); | ||
if (results === null) { | ||
// The part is not complete yet, add it to the buffer | ||
// and wait for the next chunk to arrive | ||
this.chunkBuffer += data; | ||
} else { | ||
this.chunkBuffer = ''; // Reset | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
this.chunkBuffer += data; | ||
try { | ||
for (var _iterator = results[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var part = _step.value; | ||
var _parseMultipartHttp = (0, _parseMultipartHttp2.parseMultipartHttp)(this.chunkBuffer), | ||
newBuffer = _parseMultipartHttp.newBuffer, | ||
parts = _parseMultipartHttp.parts; | ||
if (this.processedChunks === 0) { | ||
this.previousResponse = part; | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
this.previousResponse = Object.assign({}, this.previousResponse, { | ||
data: applyPatch(this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(this.previousResponse.errors, part.errors) | ||
}); | ||
this.chunkBuffer = newBuffer; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
this.onResponse(this.previousResponse); | ||
try { | ||
for (var _iterator = parts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var part = _step.value; | ||
if (this.processedChunks === 0) { | ||
this.previousResponse = part; | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
this.processedChunks += 1; | ||
this.previousResponse = Object.assign({}, this.previousResponse, { | ||
data: applyPatch(this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(this.previousResponse.errors, part.errors) | ||
}); | ||
this.onResponse(this.previousResponse); | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
this.processedChunks += 1; | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
@@ -93,0 +92,0 @@ } |
{ | ||
"name": "fetch-multipart-graphql", | ||
"version": "1.0.5", | ||
"version": "1.0.6", | ||
"description": "Cross browser function to fetch and parse streaming multipart graphql responses.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -47,3 +47,3 @@ import { PatchResolver } from '../PatchResolver'; | ||
'', | ||
'-----', | ||
'-----\r\n', | ||
].join('\r\n'); | ||
@@ -90,4 +90,7 @@ | ||
console.log('chunk1.length', chunk1.length); | ||
const chunk1a = chunk1.substr(0, 35); | ||
const chunk1b = chunk1.substr(36); | ||
const chunk1b = chunk1.substr(35, 80); | ||
const chunk1c = chunk1.substr(35 + 80); | ||
@@ -97,2 +100,4 @@ resolver.handleChunk(chunk1a); | ||
resolver.handleChunk(chunk1b); | ||
expect(onResponse).not.toHaveBeenCalled(); | ||
resolver.handleChunk(chunk1c); | ||
expect(onResponse).toHaveBeenCalledWith({ | ||
@@ -104,3 +109,3 @@ data: { viewer: { currencies: null, user: { profile: null } } }, | ||
const chunk2a = chunk2.substr(0, 35); | ||
const chunk2b = chunk2.substr(36); | ||
const chunk2b = chunk2.substr(35); | ||
@@ -122,3 +127,3 @@ resolver.handleChunk(chunk2a); | ||
const chunk3b = chunk3.substr(11, 20); | ||
const chunk3c = chunk3.substr(21); | ||
const chunk3c = chunk3.substr(11 + 20); | ||
@@ -140,2 +145,85 @@ resolver.handleChunk(chunk3a); | ||
it('should work when chunks are combined', function() { | ||
const onResponse = jest.fn(); | ||
const resolver = new PatchResolver({ | ||
onResponse, | ||
}); | ||
resolver.handleChunk(chunk1 + chunk2); | ||
expect(onResponse.mock.calls[0][0]).toEqual({ | ||
data: { viewer: { currencies: null, user: { profile: null } } }, | ||
}); | ||
expect(onResponse.mock.calls[1][0]).toEqual({ | ||
data: { | ||
viewer: { | ||
currencies: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', 'MXN'], | ||
user: { profile: null }, | ||
}, | ||
}, | ||
}); | ||
}); | ||
it('should work when chunks are combined and split', function() { | ||
const onResponse = jest.fn(); | ||
const resolver = new PatchResolver({ | ||
onResponse, | ||
}); | ||
const chunk3a = chunk3.substr(0, 10); | ||
const chunk3b = chunk3.substr(11, 20); | ||
const chunk3c = chunk3.substr(11 + 20); | ||
resolver.handleChunk(chunk1 + chunk2 + chunk3a); | ||
expect(onResponse.mock.calls[0][0]).toEqual({ | ||
data: { viewer: { currencies: null, user: { profile: null } } }, | ||
}); | ||
expect(onResponse.mock.calls[1][0]).toEqual({ | ||
data: { | ||
viewer: { | ||
currencies: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', 'MXN'], | ||
user: { profile: null }, | ||
}, | ||
}, | ||
}); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk3b); | ||
expect(onResponse).not.toHaveBeenCalled(); | ||
resolver.handleChunk(chunk3c); | ||
expect(onResponse).toHaveBeenCalledWith({ | ||
data: { | ||
viewer: { | ||
currencies: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', 'MXN'], | ||
user: { profile: { displayName: 'Steven Seagal' } }, | ||
}, | ||
}, | ||
}); | ||
}); | ||
it('should work when chunks are combined across boundaries', function() { | ||
const onResponse = jest.fn(); | ||
const resolver = new PatchResolver({ | ||
onResponse, | ||
}); | ||
const chunk2a = chunk2.substring(0, 35); | ||
const chunk2b = chunk2.substring(35); | ||
resolver.handleChunk(chunk1 + chunk2a); | ||
expect(onResponse).toHaveBeenCalledWith({ | ||
data: { viewer: { currencies: null, user: { profile: null } } }, | ||
}); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk2b); | ||
expect(onResponse).toHaveBeenCalledWith({ | ||
data: { | ||
viewer: { | ||
currencies: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', 'MXN'], | ||
user: { profile: null }, | ||
}, | ||
}, | ||
}); | ||
}); | ||
it('should merge errors', function() { | ||
@@ -175,2 +263,3 @@ const onResponse = jest.fn(); | ||
}); | ||
it('should work', function() {}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { parseMultipartHTTP } from './parseMultipartHTTP'; | ||
import { parseMultipartHttp } from './parseMultipartHttp'; | ||
@@ -32,33 +32,28 @@ // recursive function to apply the patch to the previous response | ||
this.previousResponse = null; | ||
this.processedChunks = 0; | ||
this.chunkBuffer = ''; | ||
this.processedChunks = 0; | ||
} | ||
PatchResolver.prototype.handleChunk = function(data) { | ||
const results = parseMultipartHTTP(this.chunkBuffer + data); | ||
if (results === null) { | ||
// The part is not complete yet, add it to the buffer | ||
// and wait for the next chunk to arrive | ||
this.chunkBuffer += data; | ||
} else { | ||
this.chunkBuffer = ''; // Reset | ||
for (const part of results) { | ||
if (this.processedChunks === 0) { | ||
this.previousResponse = part; | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
this.previousResponse = { | ||
...this.previousResponse, | ||
data: applyPatch(this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(this.previousResponse.errors, part.errors), | ||
}; | ||
this.chunkBuffer += data; | ||
const { newBuffer, parts } = parseMultipartHttp(this.chunkBuffer); | ||
this.chunkBuffer = newBuffer; | ||
for (const part of parts) { | ||
if (this.processedChunks === 0) { | ||
this.previousResponse = part; | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
this.previousResponse = { | ||
...this.previousResponse, | ||
data: applyPatch(this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(this.previousResponse.errors, part.errors), | ||
}; | ||
this.onResponse(this.previousResponse); | ||
} | ||
this.processedChunks += 1; | ||
this.onResponse(this.previousResponse); | ||
} | ||
this.processedChunks += 1; | ||
} | ||
}; |
163117
759