diff --git a/create-fetchival.js b/create-fetchival.js index 6b3041f..98a5fad 100644 --- a/create-fetchival.js +++ b/create-fetchival.js @@ -8,21 +8,27 @@ function query(params) { .join('&')}` } +const prevenJsonStringifyContentTypes = new Set(['application/x-sentry-envelope']) + function createFetchival({ fetch }) { async function _fetch(method, url, opts, data) { // Unlike fetchival, don't silently ignore and override if (opts.body) throw new Error('unexpected pre-set body option') + const headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...opts.headers, + } + // Allows some consumers to avoid JSON.stringify for their vendor-specific content types. + const shouldStringify = !prevenJsonStringifyContentTypes.has(headers['Content-Type']) + // Unlike fetchival, don't pollute the opts object we were given const res = await fetchival.fetch(url, { ...opts, method, - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...opts.headers, - }, - ...(data ? { body: JSON.stringify(data) } : {}), + headers, + ...(data ? { body: shouldStringify ? JSON.stringify(data) : data } : {}), }) if (res.status >= 200 && res.status < 300) { diff --git a/test/create-fetchival.js b/test/create-fetchival.js index c83d690..aa4d87e 100644 --- a/test/create-fetchival.js +++ b/test/create-fetchival.js @@ -31,4 +31,48 @@ tape('createFetchival', (t) => { t.equals(err.message, 'Couldnt establish connection') } }) + + t.test('does not stringify body for specific Conent-Type headers', async (t) => { + let capturedOpts + const formData = 'name=John&age=30' + const fetch = async (url, opts) => { + capturedOpts = opts + return { status: 200, json: async () => ({ success: true }) } + } + + const fetchival = createFetchival({ fetch }) + + await fetchival('https://example.com')('api', { + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + }).post(formData) + + t.equals( + capturedOpts.body, + formData, + 'body should not be stringified for non-JSON content type' + ) + t.equals(capturedOpts.headers['Content-Type'], 'application/x-sentry-envelope') + }) + + t.test('applies JSON.stringify to body by default', async (t) => { + let capturedOpts + const formData = 'name=John&age=30' + const fetch = async (url, opts) => { + capturedOpts = opts + return { status: 200, json: async () => ({ success: true }) } + } + + const fetchival = createFetchival({ fetch }) + + await fetchival('https://example.com')('api').post(formData) + + t.equals( + capturedOpts.body, + JSON.stringify(formData), + 'body should not be stringified for non-JSON content type' + ) + t.equals(capturedOpts.headers['Content-Type'], 'application/json') + }) })