diff --git a/package.json b/package.json index 5745607..1f5ab05 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "semantic-release": "^6.3.2" }, "dependencies": { - "apollo-client": "^0.8.2" + "apollo-client": "^0.8.2", + "object-path": "^0.11.4", + "recursive-iterator": "^2.0.3", + "uuid": "^3.0.1" } } diff --git a/src/UploadNetworkInterface.js b/src/UploadNetworkInterface.js index e2eca43..a2bb71f 100644 --- a/src/UploadNetworkInterface.js +++ b/src/UploadNetworkInterface.js @@ -1,68 +1,82 @@ -import { printAST } from 'apollo-client' -import { HTTPFetchNetworkInterface, printRequest } from 'apollo-client/transport/networkInterface' +import { printAST, HTTPFetchNetworkInterface } from 'apollo-client'; +import RecursiveIterator from 'recursive-iterator'; +import objectPath from 'object-path'; +import uuid from 'uuid'; export default function createNetworkInterface(opts) { - const { uri } = opts - return new UploadNetworkInterface(uri, opts) + const { uri } = opts; + return new UploadNetworkInterface(uri, opts); } export class UploadNetworkInterface extends HTTPFetchNetworkInterface { + constructor(...args) { + super(...args); - fetchFromRemoteEndpoint(req) { - const options = this.isUpload(req) - ? this.getUploadOptions(req) - : this.getJSONOptions(req) - return fetch(this._uri, options); - } + /** + * Save the original fetchFromRemoteEndpoint method + */ + const originalFetchFromRemoteEndpoint = this.fetchFromRemoteEndpoint.bind(this); + + /** + * Patch the fetchFromRemoteEndpoint method + */ + this.fetchFromRemoteEndpoint = ({request, options}) => { + const formData = new FormData(); + + /** + * Recursively search for File objects in + * he request and set it as formData + */ + let hasFile = false; + for (let { node, path } of new RecursiveIterator(request.variables)) { + if (node instanceof File) { + hasFile = true; - isUpload({ request }) { - if (request.variables) { - for (let key in request.variables) { - if (request.variables[key] instanceof FileList) { - return true + const id = uuid.v4(); + formData.append(id, node); + objectPath.set(request.variables, path.join('.'), id); } } - } - return false - } - getJSONOptions({ request, options }) { - return Object.assign({}, this._opts, { - body: JSON.stringify(printRequest(request)), - method: 'POST', - }, options, { - headers: Object.assign({}, { - Accept: '*/*', - 'Content-Type': 'application/json', - }, options.headers), - }) - } - - getUploadOptions({ request, options }) { - const body = new FormData() - const variables = {} + if (hasFile) { - for (let key in request.variables) { - let v = request.variables[key] - if (v instanceof FileList) { - Array.from(v).forEach(f => body.append(key, f)) + /** + * One or more Files are found, use the special fetcher + */ + return this.uploadFetchFromRemoteEndpoint({request, options}, formData); } else { - variables[key] = v + + /** + * No File is found, use the normal way + */ + return originalFetchFromRemoteEndpoint({request, options}); } - } + }; + } - body.append('operationName', request.operationName) - body.append('query', printAST(request.query)) - body.append('variables', JSON.stringify(variables)) + /** + * Alternative to `uploadFetchFromRemoteEndpoint` + * to support FormData + * + * @param {Object} requestParams + * @param {Object} requestParams.request The request + * @param {Object} requestParams.options The request-options + * @param {FormData} formData Containing the file(s) + */ + uploadFetchFromRemoteEndpoint({request, options}, formData) { + formData.append('operationName', request.operationName); + formData.append('query', printAST(request.query)); + formData.append('variables', JSON.stringify(request.variables || {})); - return Object.assign({}, this._opts, { - body, + return fetch(this._opts.uri, { + ...options, + body: formData, method: 'POST', - }, options, { - headers: Object.assign({}, { + headers: { Accept: '*/*', - }, options.headers), - }) + ...options.headers, + }, + }); } -} +};