UNPKG

expo

Version:
93 lines (84 loc) 3.35 kB
import { blobToArrayBufferAsync } from '../../utils/blobUtils'; /** * Convert FormData to Uint8Array with a boundary * * `uri` is not supported for React Native's FormData. * `blob` is not supported for standard FormData. */ export async function convertFormDataAsync( formData: FormData, boundary: string = createBoundary() ): Promise<{ body: Uint8Array; boundary: string }> { // @ts-expect-error: React Native's FormData is not compatible with the web's FormData if (typeof formData.getParts !== 'function') { throw new Error('Unsupported FormData implementation'); } // @ts-expect-error: React Native's FormData is not 100% compatible with ours const parts: ExpoFormDataPart[] = formData.getParts(); const results: (Uint8Array | string)[] = []; for (const entry of parts) { results.push(`--${boundary}\r\n`); for (const [headerKey, headerValue] of Object.entries(entry.headers)) { results.push(`${headerKey}: ${headerValue}\r\n`); } results.push(`\r\n`); if ('string' in entry) { results.push(entry.string); } else if ('file' in entry) { results.push(entry.file.bytes()); } else if ('blob' in entry) { results.push(new Uint8Array(await blobToArrayBufferAsync(entry.blob))); } else if (entry._data?.blobId != null) { // When `FormData.getParts()` is called, React Native will use the spread syntax to copy the object and lose the Blob type info. // We should find the original Blob instance from the `FormData._parts` internal properties. // @ts-expect-error: react-native's proprietary Blob type const formDatum = formData._parts?.find( ([_name, value]: [name: string, value: any]) => value.data?.blobId === entry._data.blobId ); if (formDatum == null) { throw new Error('Cannot find the original Blob instance from FormData'); } if (!(formDatum[1] instanceof Blob)) { throw new Error('Unexpected value type for Blob entry in FormData'); } results.push(new Uint8Array(await blobToArrayBufferAsync(formDatum[1]))); } else { throw new Error('Unsupported FormDataPart implementation'); } results.push(`\r\n`); } results.push(`--${boundary}--\r\n`); const arrays = results.map((result) => { if (typeof result === 'string') { // @ts-ignore: TextEncoder is not available in all environments return new TextEncoder().encode(result); } else { return result; } }) as Uint8Array[]; return { body: joinUint8Arrays(arrays), boundary }; } /** * Create mutipart boundary */ export function createBoundary(): string { const boundaryChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let boundary = '----ExpoFetchFormBoundary'; for (let i = 0; i < 16; i++) { boundary += boundaryChars.charAt(Math.floor(Math.random() * boundaryChars.length)); } return boundary; } /** * Merge Uint8Arrays into a single Uint8Array */ export function joinUint8Arrays(arrays: Uint8Array[]): Uint8Array { const totalLength: number = arrays.reduce((acc: number, arr: Uint8Array) => acc + arr.length, 0); const result: Uint8Array = new Uint8Array(totalLength); let offset: number = 0; arrays.forEach((array: Uint8Array) => { result.set(array, offset); offset += array.length; }); return result; }