fix(react-native): upload files through Expo SDK 57's fetch - #1921
Merged
Merged
Conversation
Expo SDK 57 installs expo/fetch as the global fetch. Its multipart encoder
only accepts strings, Blobs, or objects exposing bytes(), so the legacy
React Native { uri, name, type } part the SDK appended threw
"Unsupported FormDataPart implementation" before any request was sent.
Every upload part now carries both uri, which React Native's own fetch
reads natively, and a lazy bytes() that Expo's fetch encodes from. Neither
stack pays for the other's path. React Native's Blob cannot be built from
binary data, so a Blob part was not an option.
The React Native e2e suite now runs the shared upload cases through
Expo 57's real FormData patch and body encoder, transpiled from the
installed expo package at build time.
Contributor
|
Member
Author
|
@greptile review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
File uploads from the React Native SDK fail on Expo SDK 57 with:
The error is raised on the device before any bytes leave it. It affects every upload the SDK performs, including
storage.createFile, for both small files and chunked large files. Reported by a user who upgraded from Expo 54 to 57 and whose profile photo upload started failing with no other change.Which versions are affected
fetchexpo/fetchwas opt-in)expo/fetch(WinterCG) installed asglobalThis.fetchEXPO_PUBLIC_USE_RN_FETCH=1Expo made the switch in
packages/expo/src/winter/runtime.native.ts:What the SDK was sending
React Native's
FormDatais not the web one. Its "file" part has always been a plain object with auri, which React Native's native networking layer opens and streams itself:The SDK built exactly that object in three places in
templates/react-native/src/services/template.ts.twig: the whole-file branch for files up to 5 MB, the first chunk of a large upload, and every subsequent chunk. For chunks it first read the slice as base64 withexpo-file-system, then handed it back as adata:URI on iOS or as a temp file path on Android, always through theurifield.Expo's encoder,
packages/expo/src/winter/fetch/convertFormData.ts, builds the multipart body in JavaScript and has no idea what auriis:The SDK's
call()wraps every thrown error inAppwriteException, which is why users see it as an Appwrite error even though nothing reached the server.Why not a
BlobThe obvious fix is to append a
Blob, which both stacks accept. It does not work on React Native.BlobManager.createFromPartsin React Native 0.83 rejects binary input outright:A React Native
Blobcan only be made from strings or other native-backed Blobs, so there is no way to turn a chunk we read from disk into one.What the fix does
Every upload part now satisfies both encoders at once. It keeps
urifor React Native's networking layer and adds a lazybytes()that Expo's encoder calls. Each stack reads only its own field, so React Native's fetch never decodes base64 and Expo's fetch never touches theuri.The three call sites pass a reader instead of eagerly reading. Chunks already hold their base64 slice, so they return it. The whole-file branch, which previously passed the caller's object straight through, reads on demand:
Expo's own header builder already reads
nameandtypeoff any object part, soContent-Disposition: form-data; name="file"; filename="…"and the part'sContent-Typecome out the same on both stacks.Verification against the live API
The generated SDK was run in Node against
https://sgp.cloud.appwrite.io/v1through Expo 57.0.24's realinstallFormDataPatchandnormalizeBodyInitAsync, imported from the publishedexpopackage, withreact-nativeandexpo-file-systemstubbed. Each run uploaded, downloaded, compared bytes, and deleted.Unsupported FormDataPart implementationUnsupported FormDataPart implementationRegression test
The React Native e2e suite now exercises the shared
Base::UPLOAD_RESPONSEandBase::LARGE_FILE_RESPONSEScases that every other language already asserts. The harness underneath is what makes them meaningful for this bug:shims/expo-runtime.jsinstalls a React Native styleFormData, applies Expo'sinstallFormDataPatch, and routes multipart bodies through Expo'snormalizeBodyInitAsyncbefore the browser's fetch sends the bytes. Expo publishes these as TypeScript sources only, so the build step transpiles them out ofnode_modules/expowith esbuild. The test tracks the real Expo package rather than a vendored copy.shims/expo-file-system.jsserves the fixtures over the test HTTP server soreadAsStringAsyncwithpositionandlengthbehaves like it does on a device.On
mainthe suite fails with the reporter's exact error. With this PR it passes.Workaround for users on current releases
Set
EXPO_PUBLIC_USE_RN_FETCH=1in.envand rebuild. This restores React Native's fetch as the global and the current SDK release works unchanged.Out of scope
package.jsonstill pinsexpo-file-systemto18.*.*, which is the Expo 52 line. Expo 57 shipsexpo-file-system@57and movedreadAsStringAsynctoexpo-file-system/legacy. That pin will also bite Expo 57 users at install time and should be widened in its own change.