Skip to content

Instantly share code, notes, and snippets.

@bendman
Last active February 6, 2017 19:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bendman/50d5b549bf29c4ed30cd0c92c38f9763 to your computer and use it in GitHub Desktop.
Save bendman/50d5b549bf29c4ed30cd0c92c38f9763 to your computer and use it in GitHub Desktop.
Proxy `fetch` in a way that automatically handles file uploads and/or JSON parsing/serializing
import fetch from './fetch-proxy.js';
export async function saveImage(imageURI) {
return await fetch('user/picture', {
method: 'PUT',
body: { photoContent: imageURI },
fileKeys: ['photoContent'],
});
}
// Proxy `fetch` in a way that automatically handles file uploads and/or JSON parsing/serializing
// The most important part here is the default export, `apiFetch(source, options)`.
const getExtension = (uri) => {
const fileExtension = uri.match(/[^\.]+$/);
if (fileExtension && fileExtension.length) {
return fileExtension[0];
}
return null;
}
const getMimeType = (uri) => {
const mimeTypes = {
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
pdf: 'application/pdf',
};
const fileExtension = getExtension(uri);
if (fileExtension && mimeTypes.hasOwnProperty(fileExtension)) {
return mimeTypes[fileExtension];
}
console.warn(`No Mime Type for file "${uri}"`);
return null;
}
const buildOutboundBody = ({ body, fileKeys }) => {
// Body needs no transformation
if (!body || body instanceof FormData || typeof body === 'string') return body;
// Body includes a file upload and must be converted to FormData
if (fileKeys) {
return Object.entries(body).reduce((formData, [key, value]) => {
if (fileKeys.includes(key) && value) {
// File value, include as data structure
formData.append(key, {
type: getMimeType(value),
name: `file.${getExtension(value)}`,
uri: value,
});
} else if (value) {
// Normal value, include in the form
formData.append(key, value);
}
return formData;
}, new FormData());
}
// Body is JSON and must be stringified
return JSON.stringify(body);
}
export default async function apiFetch(source, options = {}) {
let fetchOpts, res, resBody;
try {
const body = buildOutboundBody(options);
// Omit fileKeys from options sent to `fetch`
const { fileKeys, ...passThroughOpts } = options;
fetchOpts = {
...passThroughOpts,
body,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...(options.headers || {}),
}
};
// FormData requires a special content header
if (body && body instanceof FormData) fetchOpts.headers['Content-Type'] = 'multipart/form-data';
res = await fetch(source, fetchOpts);
resBody = await res.json();
if (!res.ok) {
throw new Error();
}
return { data: resBody.data, msg: resBody.msg };
} catch (error) {
// Add custom error handling here, for example attaching the response
throw error;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment