React Native: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'. at new ApolloError - react-native

I am trying to upload image from my react native app to graphql by using Apollo client with createUploadLink(). When I am trying to mutate data by passing a ReactNativeFile as a variable, then it says
"network request failed: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'. at new ApolloError ".
This this the mutation which i am trying to use
mutation publishPost(
$content: String!
$LocationInput: LocationInput!
$InputPostAttachment: [InputPostAttachment!]
) {
publishPost(
content: $content
location: $LocationInput
attachments: $InputPostAttachment
) {
content
}
}
InputPostAttachment has type
type InputPostAttachment {
type: PostAttachmentType!
file: Upload!
}
Apollo client settings and i am using apollo-upload-client
const httpLink = createUploadLink({
uri: 'http://localhost:8000/graphql',
});
const authLink = setContext(async (headers: any) => {
const token = await getToken();
return {
...headers,
headers: {
authorization: token ? `Bearer ${token}` : null,
},
};
});
const link = authLink.concat(httpLink);
// create an inmemory cache instance for caching graphql data
const cache = new InMemoryCache();
// instantiate apollo client with apollo link instance and cache instance
export const client = new ApolloClient({
link,
cache,
});
File upload Function and i am using react-native-image-crop-picker for multi image selection
const [image, setimage] = useState([]);
const _pickImage = () => {
ImagePicker.openPicker({
includeBase64: true,
multiple: true,
}).then((images: any) => {
let imageData: any = [];
images.map((data: any) => {
const file = new ReactNativeFile({
uri: data.path,
name: data.filename,
type: data.mime,
});
imageData.push({
type: 'IMAGE',
file: file,
});
});
setimage(imageData);
console.log(images);
});
};
const handlePost = async () => {
const InputPostAttachment: any = [...image];
const LocationInput = {
place: place,
vicinity: vicinity,
province: province,
};
publishPost({variables: {content, LocationInput, InputPostAttachment}})
.then(({data}) => {
console.log(data);
props.navigation.navigate('Home');
})
.catch((err) => {
console.log('err happened');
console.log(err);
});
};
could someone please help me out from this?

In addition to the chrome debugger issue, this error also happens on the expo web.
To anyone uploading images on expo web (or react-native web), here's a working solution:
/** Load image from camera/roll. */
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
quality: 1,
});
if (result.cancelled) {
return;
}
/** web platform: blob. */
const convertBase64ToBlob = async (base64) => {
const response = await fetch(base64);
const blob = await response.blob();
return blob;
};
/** android/ios platform: ReactNativeFile.*/
const createReactNativeFile = (uri) => {
const file = new ReactNativeFile({
uri,
type: mime.lookup(uri) || 'image',
name: `file-${Date.now()}`,
});
return file;
};
/** Use blob for web, ReactNativeFile otherwise. */
const file = Platform.OS === 'web'
? await convertBase64ToBlob(result.uri)
: createReactNativeFile(result.uri);
/** Upload image with apollo. */
mutate({ variables: { file } });
On the web platform, ImagePicker returns a base64 value instead of a file path. This problem doesn't happen if the platform is Android or iOS, as ImagePicker returns a file path, which is expected by apollo-upload-client.
The solution is to detect if the URI is base64 (which happens when the platform is "web") and convert it to a blob.

My apollo-client was configured using apollo-boost and i was using chrome debugger to intercept the network was causing me this issue.
To be more specific I was using the below code to get the network requests sent by my app in the chrome debugger
global.XMLHttpRequest =
global.originalXMLHttpRequest || global.XMLHttpRequest;
global.FormData = global.originalFormData || global.FormData;
if (window.FETCH_SUPPORT) {
window.FETCH_SUPPORT.blob = false;
} else {
global.Blob = global.originalBlob || global.Blob;
global.FileReader = global.originalFileReader || global.FileReader;
}
apollo-upload-client wont send the data in multipart data if we are using chrome debugger. We will face network issue.This issue has the answer. or I had not removed apollo-boost and some part of my app was using it that was also a issue.

Related

Next.js API's config object is not working on production level

Hy,
I'm working on project. I just want to disable the native bodyparcer of next.js and use formidable's parse for incoming requests. I am sending file and some other data from frontend and saveing file on my file system and other data in mongodb.
I did a following configuration for it in that end point.
export const config = {
api: {
bodyParser: false,
},
}
The full code is here.
const fs = require("fs");
const path = require("path")
const decompress = require("decompress");
import formidable from "formidable";
import connect from "../../../database/connect";
import Proposal from "../../../database/models/propSchema";
const directoryPath = e => path.join(process.cwd(), e),
videosDir = directoryPath("/public/videos")
export default function handler(req, res){
res.setHeader("Content-Type", "application/json");
if(req.method === 'POST')
{
console.log(req.method)
const form = new formidable.IncomingForm();
form.parse(req,(err, fields, files) => {
if(err && !files.file){
return res.status(404).json({status: false, error: "Error: Request is not parsed correctly"})
}
console.log("----------------------------",form._parser.globalOptions.maxFileSize)
console.log(files, fields);
const {client,published} = fields,
id = Math.random().toString(36).substring(2) +
(new Date()).getTime().toString(36);
decompress(files.zipFile.filepath, videosDir + "/" + id)
.then(files => {
console.log(files)
console.log(`Proposal API is called with following queries id:${id} and client:${JSON.stringify(client)}`)
connect().catch(error => console.log(error))
const proposal = new Proposal({id,client,published: published? JSON.parse(published) : true})
proposal.save()
.then(()=>{
res.status(200).json(proposal)
})
})
.catch((err) => {
console.log(err);
const path = `${videosDir}/${id}`;
if(fs.existsSync(path)){
fs.readdirSync(path).forEach((file) => fs.unlinkSync(path + "/" + file));
fs.rmdirSync(path);
}
res.status(400).json({
status: "fail",
info: `${id} cannot be compress and Published`,
err,
});
});
});
}
else{
res.setHeader('Allow', 'POST');
res.status(403).json({status: false , discription: "This method is not allowed." })
}
}
export const config = {
api: {
bodyParser: false,
},
}
I make another endpoint to make code simple to understand and remove all code that is unrelative to this error. The same error is occuring with both endpoints.
export const config = {
api: {
bodyParser: false,
},
}
export default function handler(req, res){
if(req.method !== 'POST')
{
res.setHeader('Allow', 'POST');
return res.status(403).json({status: false , discription: "This method is not allowed." })
}
res.status(200).json({status: true});
};
So, When I run it on my local environment and send data, it works fine but in production, it gives a "413 Request Entity Too Large" error. This is because I think the config object is not working correctly on a production level.
Because 1 MB is the maximum file limit of next.js body-parser and when I try to upload a file less than 1 MB it also works fine in production and local system. But when exceeding that limit it gives that error.
And I also try in my local system to send large files without the above configuration, in this case, it gives the same error.
Is this error in the config object? and who can I solve this error?Please help me also to understand it. Thanks

React Native Image Upload with FormData to NestJS server using a FileInterceptor, but 'file' is undefined

My React Native application receives a selected image using the react-native-image-picker library and I need to send that image to a back-end running a NestJS server. The endpoint uses #UseInterceptor and FileInterceptor to extract the image from the 'file' field of the formData received. However, when I fire the request to the endpoint, the file received is undefined.
Here is my React Native code sending the request with the file in the FormData as a payload.
const uploadNewProfileImage = async () => {
if (!newProfileImage?.assets) return;
const formData = new FormData();
const profileImage = newProfileImage.assets[0];
console.log(profileImage);
if (profileImage.uri && user) {
formData.append(
'file',
JSON.stringify({
uri:
Platform.OS === 'android'
? profileImage.uri
: profileImage.uri.replace('file://', ''),
name: profileImage.fileName,
type: profileImage.type
})
);
client // client is an Axios instance that injects Bearer Token
.post(`/user/profile/${user.uid}/image`, formData)
.then(({ data }) => {
console.log(data);
})
.catch((err) => {
console.log(err.response);
setShowImageUploadError(true);
})
.finally(() => {
getUserProfile();
});
}
};
Here is my back-end NestJS code extracting the file.
// User.controller.ts
#UseGuards(UserGuard)
#ApiBearerAuth()
#ApiUnauthorizedResponse({ description: 'Unauthorized' })
#UseInterceptors(FileInterceptor('file', { limits: { fileSize: 20000000 } }))
#Post('/profile/:uid/image')
#ApiOkResponse({ type: UploadProfileResponse })
#ApiBadRequestResponse({ description: 'Image too large OR Invalid image type' })
async uploadProfilePicture(#UploadedFile() file: Express.Multer.File, #Request() req): Promise<UploadProfileResponse> {
const uid = req.user.uid;
const imageUrl = await this.userService.uploadProfilePicture(uid, file);
return imageUrl;
}
}
I tried to set the axios request header in the axios config like so
{
headers: {
'Content-Type': 'multipart/form-data; boundary=——file'
}
}
I tried chaning the back-end endpoint to the following
#UseGuards(UserGuard)
#ApiBearerAuth()
#ApiUnauthorizedResponse({ description: 'Unauthorized' })
#UseInterceptors(FileFieldsInterceptor([{ name: 'file' }], { limits: { fileSize: 20000000 } }))
#Post('/profile/:uid/image')
#ApiOkResponse({ type: UploadProfileResponse })
#ApiBadRequestResponse({ description: 'Image too large OR Invalid image type' })
async uploadProfilePicture(#UploadedFiles() file: Array<Express.Multer.File>, #Request() req): Promise<UploadProfileResponse> {
const uid = req.user.uid;
console.log("File", file);
const imageUrl = await this.userService.uploadProfilePicture(uid, file[0]);
return imageUrl;
}
Nothing seems to be working, and the file extracted from the backend is still undefined.
Any help would be greatly appreciated.

React native content uri to base64 string

I'm trying to upload files using RN Document Picker.
Once I get those files selected, I need to turn them to base64 string so I can send it to my API.
const handlePickFiles = async () => {
if (await requestExternalStoreageRead()) {
const results = await DocumentPicker.pickMultiple({
type: [
DocumentPicker.types.images,
DocumentPicker.types.pdf,
DocumentPicker.types.docx,
DocumentPicker.types.zip,
],
});
const newUploadedFile: IUploadedFile[] = [];
for (const res of results) {
console.log(JSON.stringify(res, null, 2));
newUploadedFile.push({
name: res.name,
type: res.type as string,
size: res.size as number,
extension: res.type!.split('/')[1],
blob: res.uri, <<-- Must turn this in base64 string
});
}
setUploadedFiles(newUploadedFile);
console.log(newUploadedFile);
}
}
};
The document picker returns content uri (content://...)
They lists this as an example of handling blob data and base64:
let data = new FormData()
data.append('image', {uri: 'content://path/to/content', type: 'image/png', name: 'name'})
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: data
})
Where they basically say that you don't need to use blob or base64 when using multipart/form-data as content type. However, my graphql endpoint cannot handle multipart data and I don't have time to rewrite the whole API. All I want is to turn it to blob and base64 string, even if other ways are more performant.
Searching for other libraries, all of them are no longer maintained, or has issues with new versions of android. RN Blob Utils is the latest npm that was no longer maintained.
I tried to use RN Blob Utils but I either get errors, wrong data type, or the file uploads but is corrupted.
Some other things I found is that I can use
fetch(res.uri).then(response => {response.blob()})
const response = await ReactNativeBlobUtil.fetch('GET', res.uri);
const data = response.base64();
ReactNativeBlobUtil.wrap(decodeURIComponent(blob))
///----
const blob = ReactNativeBlobUtil.fs.readFile(res.uri, 'base64');
But I can't do anything with that blob file.
What is the simplest way to uplaod files from document picker as base64 format? Is it possible to avoid using external storage permission?
You don't need to the third-party package to fetch BLOB data
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", "[LOCAL_FILE_PATH]", true);
xhr.send(null);
});
// Code to submit blob file to server
// We're done with the blob, close and release it
blob.close();
I ended up using react-native-blob-util
const res = await DocumentPicker.pickSingle({
type: [
DocumentPicker.types.images,
DocumentPicker.types.pdf,
DocumentPicker.types.docx,
DocumentPicker.types.zip,
],
});
const newUploadedFile: IUploadedFile[] = [];
const fileType = res.type;
if (fileType) {
const fileExtension = fileType.substr(fileType.indexOf('/') + 1);
const realURI = Platform.select({
android: res.uri,
ios: decodeURI(res.uri),
});
if (realURI) {
const b64 = await ReactNativeBlobUtil.fs.readFile(
realURI,
'base64',
);
const filename = res.name.replace(/\s/g, '');
const path = uuid.v4();
newUploadedFile.push({
name: filename,
type: fileType,
size: res.size as number,
extension: fileExtension,
blob: b64,
path: Array.isArray(path) ? path.join() : path,
});
} else {
throw new Error('Failed to process file');
}
} else {
throw new Error('Failed to process file');
}

Why when I upload file with apollo-server the file is uploaded but the file is 0kb?

I tried to solve the problem but I don't understand why the file is uploaded but his size is 0Kb.
I see this code in the tutorial but he works on that tutorial but, is not worked for me
const { ApolloServer, gql } = require('apollo-server');
const path = require('path');
const fs = require('fs');
const typeDefs = gql`
type File {
url: String!
}
type Query {
hello: String!
}
type Mutation {
fileUpload(file: Upload!): File!
}
`;
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
Mutation: {
fileUpload: async (_, { file }) => {
const { createReadStream, filename, mimetype, encoding } = await file;
const stream = createReadStream();
const pathName = path.join(__dirname, `/public/images/${filename}`);
await stream.pipe(fs.createWriteStream(pathName));
return {
url: `http://localhost:4000/images/${filename}`,
};
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
then when I upload the file, it is uploaded, but the file is 0kb
like this
What is happening is the resolver is returning before the file has uploaded, causing the server to respond before the client has finished uploading. You need to promisify and await the file upload stream events in the resolver.
Here is an example:
https://github.com/jaydenseric/apollo-upload-examples/blob/c456f86b58ead10ea45137628f0a98951f63e239/api/server.js#L40-L41
In your case:
const resolvers = {
Query: {
hello: () => "Hello world!",
},
Mutation: {
fileUpload: async (_, { file }) => {
const { createReadStream, filename } = await file;
const stream = createReadStream();
const path = path.join(__dirname, `/public/images/${filename}`);
// Store the file in the filesystem.
await new Promise((resolve, reject) => {
// Create a stream to which the upload will be written.
const writeStream = createWriteStream(path);
// When the upload is fully written, resolve the promise.
writeStream.on("finish", resolve);
// If there's an error writing the file, remove the partially written
// file and reject the promise.
writeStream.on("error", (error) => {
unlink(path, () => {
reject(error);
});
});
// In Node.js <= v13, errors are not automatically propagated between
// piped streams. If there is an error receiving the upload, destroy the
// write stream with the corresponding error.
stream.on("error", (error) => writeStream.destroy(error));
// Pipe the upload into the write stream.
stream.pipe(writeStream);
});
return {
url: `http://localhost:4000/images/${filename}`,
};
},
},
};
Note that it’s probably not a good idea to use the filename like that to store the uploaded files, as future uploads with the same filename will overwrite earlier ones. I'm not really sure what will happen if two files with the same name are uploaded at the same time by two clients.

Convert Base64 to png and save in the device React Native Expo

I've been trying to save an image on a mobile device with React Native and Expo, i have tried with these packages:
import RNFetchBlob from 'react-native-fetch-blob';
import RNfs from 'react-native-fs ';
but both give me this error when implementing them
null is not an object (evaluating 'RNFetchBlob.DocumentDir')
then try expo-file-system but i don't see any clear example of how to convert base64 and download it to mobile
UPDATE
I was able to do it, my purpose was to save the base64 of a QR and convert it to png and at the same time be able to share it, I did it using expo-file-system and expo-sharing
this is mi code,
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
//any image, I use it to initialize it
const image_source = 'https://images.unsplash.com/photo-1508138221679-760a23a2285b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80';
share=()=>{
var self = this;
self.setState({loading:true})
FileSystem.downloadAsync(
image_source,
FileSystem.documentDirectory + '.png'
)
.then(({ uri }) => {
console.log(self.state.base64Code);
FileSystem.writeAsStringAsync(
uri,
self.state.base64Code,
{'encoding':FileSystem.EncodingType.Base64}
)
.then(( ) => {
this.setState({loading:false})
Sharing.shareAsync(uri);
})
})
.catch(error => {
console.error(error);
});
}
Actually, I don't know if it's the best way, first write a png image in the directory and then rewrite it with the base64 code, but it worked
This worked for me:
const data = "data:image/png;base64,ASDFASDFASDf........"
const base64Code = data.split("data:image/png;base64,")[1];
const filename = FileSystem.documentDirectory + "some_unique_file_name.png";
await FileSystem.writeAsStringAsync(filename, base64Code, {
encoding: FileSystem.EncodingType.Base64,
});
const mediaResult = await MediaLibrary.saveToLibraryAsync(filename);
Thanks for the update. I've been struggling for days with this on Android and base64 images!
In my case i was trying to upload a base64 image from a signature pad on expo and always got "Network request failed"
I managed to make it work like this hope it helps!
import * as FileSystem from 'expo-file-system';
const uploadBase64 = async (base64String) => {
this.setState({ uploading: true });
//Without this the FilySystem crashes with 'bad base-64'
const base64Data = base64String.replace("data:image/png;base64,","");
try {
//This creates a temp uri file so there's no neeed to download an image_source to get a URI Path
const uri = FileSystem.cacheDirectory + 'signature-image-temp.png'
await FileSystem.writeAsStringAsync(
uri,
base64Data,
{
'encoding': FileSystem.EncodingType.Base64
}
)
//At this point the URI 'file://...' has our base64 image data and now i can upload it with no "Network request failed" or share the URI as you wish
const uploadResult = await this.uploadImageAsync(uri).then(res => res.json())
if (uploadResult) {
this.setState({ image: uploadResult.location });
}
this.setState({ uploading: false });
} catch (e) {
this.setState({ uploading: false });
console.log('*Error*')
console.log(e)
}
}
//Just and ordinary upload fetch function
const uploadImageAsync = (uri) => {
let apiUrl = 'https://file-upload-example-backend-dkhqoilqqn.now.sh/upload';
let formData = new FormData();
formData.append('photo', {
uri,
name: `photo.png`,
type: `image/png`,
});
let options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
return fetch(apiUrl, options);
}