jpg image file can't be open after OSS upload

89 views Asked by At

The React Native 0.70 app uses react-native-blob-util to read jpg image file from image picker before uploading it to OSS. Here is the code:

import ReactNativeBlobUtil from "react-native-blob-util"; //v0.19
const fileData = await ReactNativeBlobUtil.fs.readFile(filePath, 'base64'); //filePath is the file path from image picker
const fileData1 = `data:image/jpeg;base64,${fileData}`

The fileData1 was uploaded with fetch:

const response = await fetch(preSignedUrl, {. //preSignedUrl is acquired from backend node server
    method: 'PUT',
    headers: { 'Content-Type': 'image/jpeg' },
    body: fileData1,
  })

The image uploaded can't be open. The md5(fileData1) before fetch PUT is the same as the md5 returned from OSS. It may be related to the jpg file format on OSS but not sure where is the issue.

UPDATE: return of await fetch(filePath) (fetch fileData1 throws error):

LOG  responsefetch in uploadTOOSS:  {"_bodyBlob": {"_data": {"__collector": [Object], "blobId": "44313295-9de3-4dfe-b9be-bbdef6110f4b", "lastModified": 0, "name": "rn_image_picker_lib_temp_9373b67e-6f10-4932-b743-f2155da4fca8.jpg", "offset": 0, "size": 47367, "type": "image/jpeg"}}, "_bodyInit": {"_data": {"__collector": [Object], "blobId": "44313295-9de3-4dfe-b9be-bbdef6110f4b", "lastModified": 0, "name": "rn_image_picker_lib_temp_9373b67e-6f10-4932-b743-f2155da4fca8.jpg", "offset": 0, "size": 47367, "type": "image/jpeg"}}, "bodyUsed": false, "headers": {"map": {"content-type": "image/jpeg"}}, "ok": false, "status": 0, "statusText": "", "type": "default", "url": ""}
1

There are 1 answers

6
VonC On BEST ANSWER

You are uploading the base64-encoded image data directly to the OSS (Object Storage Service) using a pre-signed URL.

When you encode the file content in base64 and prepend the data URL schema (data:image/jpeg;base64), it is suitable for embedding images directly into web pages or CSS files (see "How to display Base64 images in HTML?" for instance).
However, for uploading to OSS, you should convert the base64 back to binary data because the storage expects the raw binary format of the image, not its base64-encoded representation.

So you need to convert the base64-encoded string back to binary before uploading. That can be done using the Blob or Buffer API, but since you are working in a React Native environment, you will have to rely on what is available there. Unfortunately, React Native does not have the same global Blob constructor as web browsers, but react-native-blob-util provides a workaround.

import ReactNativeBlobUtil from "react-native-blob-util";

// Step 1: Convert base64 to binary
const blob = await ReactNativeBlobUtil.base64ToBlob(fileData);

// Step 2: Upload the blob
const response = await fetch(preSignedUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: blob, // Use the blob directly
});

You need to make sure base64ToBlob correctly converts the base64 string to a binary format that fetch can handle. If react-native-blob-util does not directly support base64ToBlob, you might have to use fetch to create a blob from the base64 string, like:

// Convert base64 to blob using fetch
const response = await fetch(fileData1);
const blob = await response.blob();

// Then upload this blob as before

Unfortunately ReactNativeBlobUtil.base64ToBlob does not exist in the module.
fetch(fileData1) throws error of network request failed.
Converting base64 to blob shall be a way to go. Just a matter of how to do it in React Native.

Since ReactNativeBlobUtil.base64ToBlob does not exist, and that the fetch(fileData1) approach results in a network error, you might consider instead to manually converting the base64 string to binary data, and then upload this binary data using fetch.

Try and create a function to convert the base64 string to a binary format that fetch can handle as binary data, using the Uint8Array:

// Helper function to convert base64 to Uint8Array
function base64ToUint8Array(base64) {
  const binaryString = atob(base64); // Decode base64
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
}

// Assuming `fileData` is your base64-encoded image data without the data URL prefix
const binaryData = base64ToUint8Array(fileData);

// Upload binary data
const response = await fetch(preSignedUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: binaryData, // Use the binary data directly
});

That would manually convert the base64-encoded string to a binary format (Uint8Array) that is then uploaded.
It is a bit lower level and circumvents the need for a Blob, which should be a good fit considering the capabilities and limitations of React Native's environment.

The atob function used in the conversion process is a built-in JavaScript function that decodes a base64-encoded string. However, it might not be available in all React Native environments. If atob is not available in your React Native environment, you might need to use a polyfill or an alternative method to decode the base64 string. Libraries such as react-native-blob-util might offer similar functionality, or you can use a third-party library to decode base64 strings.

The OP reports in the comments making it work:

Here is the one worked to overcome atob.

  • install base-64 and import {decode, encode} from 'base-64'.
  • Do const binaryString = decode(base64); in the function above.

The rest works like charm..