Skip to content

Commit

Permalink
Merge pull request #4 from bytewitchcraft/feature/pass-headers-throgh…
Browse files Browse the repository at this point in the history
…-middlware

added passing headers from context
  • Loading branch information
ihorkatkov authored Feb 11, 2018
2 parents bb181ae + c534e13 commit 5690a43
Show file tree
Hide file tree
Showing 11 changed files with 2,939 additions and 85 deletions.
6 changes: 6 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": ["env"],
"plugins": [
"transform-object-rest-spread"
]
}
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"parser": "babel-eslint",
"plugins": [
"prettier"
],
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"repository": "https://github.com/bytewitchcraft/apollo-absinthe-upload-link",
"author": "Ihor Katkov",
"license": "MIT",
"scripts": {
"test": "jest"
},
"dependencies": {
"apollo-client": "^2.0.4",
"apollo-link": "^1.0.7",
Expand All @@ -15,8 +18,15 @@
"rxjs": "5.4.3"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-jest": "^22.2.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"eslint": "^4.14.0",
"eslint-plugin-prettier": "^2.4.0",
"prettier": "1.9.2"
"jest": "^22.2.1",
"prettier": "1.9.2",
"regenerator-runtime": "^0.11.1"
}
}
1 change: 1 addition & 0 deletions src/__mocks__/extractFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => ({ variables: {}, files: [{ file: {}, name: 'file' }] })
6 changes: 6 additions & 0 deletions src/__mocks__/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default jest.fn(opts => ({
url: opts.url,
method: 'POST',
body: opts.body,
headers: opts.headers,
}))
50 changes: 50 additions & 0 deletions src/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createUploadMiddleware } from '../index'

jest.mock('../request')
jest.mock('../extractFiles')

const generateOperations = (context = { headers: {} }) => ({
variables: {},
getContext: () => context,
})

describe('#createUploadMiddleware', () => {
it('should pass headers from context', () => {
const { request } = createUploadMiddleware({ uri: 'http://example.com' })
const headers = { authorization: '1234' }
const operations = generateOperations({ headers })
const forward = jest.fn()

const result = request(operations, forward)

expect(forward).toHaveBeenCalledTimes(0)
expect(result.headers).toEqual(headers)
})
it('should pass headers from options', () => {
const headers = { authorization: '1234' }
const { request } = createUploadMiddleware({
uri: 'http://example.com',
headers,
})
const operations = generateOperations()
const forward = jest.fn()

const result = request(operations, forward)

expect(result.headers).toEqual(headers)
})
it('should combine headers from options and context', () => {
const optionsHeaders = { 'x-spree-token': 'token' }
const contextHeaders = { authorization: '1234' }
const { request } = createUploadMiddleware({
uri: 'http://example.com',
headers: optionsHeaders,
})
const operations = generateOperations({ headers: contextHeaders })
const forward = jest.fn()

const result = request(operations, forward)

expect(result.headers).toEqual({ ...contextHeaders, ...optionsHeaders })
})
})
33 changes: 33 additions & 0 deletions src/extractFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { pipe, append, join } from 'ramda'
import { isFileList, isObject, isUploadFile } from './validators'

const extractFiles = variables => {
const files = []
const walkTree = (tree, path = []) => {
const mapped = Array.isArray(tree) ? tree : Object.assign({}, tree)
Object.keys(mapped).forEach(key => {
const value = mapped[key]
const name = pipe(append(key), join('.'))(path)

if (isUploadFile(value) || isFileList(value)) {
const file = isFileList(value)
? Array.prototype.slice.call(value)
: value

files.push({ file, name })
mapped[key] = name
} else if (isObject(value)) {
mapped[key] = walkTree(value, name)
}
})

return mapped
}

return {
files,
variables: walkTree(variables),
}
}

export default extractFiles
56 changes: 10 additions & 46 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,28 @@
import { HttpLink } from 'apollo-link-http'
import { ApolloLink, concat } from 'apollo-link'
import { printAST } from 'apollo-client'
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/dom/ajax';
import 'rxjs/add/operator/map';
import { pipe, append, join } from 'ramda'
import request from './request'
import extractFiles from './extractFiles'
import { isObject } from './validators'

const isObject = value => value !== null && typeof value === 'object'

const isFileList = value =>
typeof FileList !== 'undefined' && value instanceof FileList

const isUploadFile = value =>
typeof File !== 'undefined' && value instanceof File

const extractFiles = variables => {
const files = []
const walkTree = (tree, path = []) => {
const mapped = Array.isArray(tree) ? tree : Object.assign({}, tree)
Object.keys(mapped).forEach(key => {
const value = mapped[key]
const name = pipe(append(key), join('.'))(path)

if (isUploadFile(value) || isFileList(value)) {
const file = isFileList(value)
? Array.prototype.slice.call(value)
: value

files.push({ file, name })
mapped[key] = name
} else if (isObject(value)) {
mapped[key] = walkTree(value, name)
}
})

return mapped
}

return {
files,
variables: walkTree(variables),
}
}

const createUploadMiddleware = ({ uri }) =>
export const createUploadMiddleware = ({ uri, headers }) =>
new ApolloLink((operation, forward) => {
if (typeof FormData !== 'undefined' && isObject(operation.variables)) {
const { variables, files } = extractFiles(operation.variables)

if (files.length > 0) {
const { headers: contextHeaders } = operation.getContext()
const formData = new FormData()

formData.append('query', printAST(operation.query))
formData.append('variables', JSON.stringify(variables))
files.forEach(({ name, file }) => formData.append(name, file))

return Observable.ajax({
url: uri,
return request({
uri,
body: formData,
method: 'POST',
}).map(({ response }) => response)
headers: { ...contextHeaders, ...headers },
})
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/dom/ajax'
import 'rxjs/add/operator/map'

/**
* Request function
*
* @param {Object} opts
*/
const request = opts =>
Observable.ajax({
url: opts.uri,
body: opts.body,
method: 'POST',
headers: opts.headers,
}).map(({ response }) => response)

export default request
6 changes: 6 additions & 0 deletions src/validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const isObject = value => value !== null && typeof value === 'object'
export const isFileList = value =>
typeof FileList !== 'undefined' && value instanceof FileList

export const isUploadFile = value =>
typeof File !== 'undefined' && value instanceof File
Loading

0 comments on commit 5690a43

Please sign in to comment.