[FIX] Filenames are incorrect in non-latin alphabets on upload (#2671)

* fix: filename on react-native-image-crop-picker

* fix: use rn-fetch-blob to upload files

* fix: FileUpload as a service

* fix: cancel upload on iOS

* fix: file upload from share extension

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Djorkaeff Alexandre 2020-12-01 17:19:48 -03:00 committed by GitHub
parent b0b9d62a91
commit 2403eb3857
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 30 deletions

View File

@ -1,6 +1,7 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import FileUpload from '../../utils/fileUpload';
import database from '../database';
import log from '../../utils/log';
@ -12,7 +13,11 @@ export function isUploadActive(path) {
export async function cancelUpload(item) {
if (uploadQueue[item.path]) {
uploadQueue[item.path].abort();
try {
await uploadQueue[item.path].cancel();
} catch {
// Do nothing
}
try {
const db = database.active;
await db.action(async() => {
@ -32,9 +37,6 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
const uploadUrl = `${ server }/api/v1/rooms.upload/${ rid }`;
const xhr = new XMLHttpRequest();
const formData = new FormData();
fileInfo.rid = rid;
const db = database.active;
@ -56,31 +58,38 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
}
}
uploadQueue[fileInfo.path] = xhr;
xhr.open('POST', uploadUrl);
formData.append('file', {
uri: fileInfo.path,
const formData = [];
formData.push({
name: 'file',
type: fileInfo.type,
name: encodeURI(fileInfo.name) || 'fileMessage'
filename: fileInfo.name || 'fileMessage',
uri: fileInfo.path
});
if (fileInfo.description) {
formData.append('description', fileInfo.description);
formData.push({
name: 'description',
data: fileInfo.description
});
}
if (tmid) {
formData.append('tmid', tmid);
formData.push({
name: 'tmid',
data: tmid
});
}
xhr.setRequestHeader('X-Auth-Token', token);
xhr.setRequestHeader('X-User-Id', id);
const { customHeaders } = RocketChatSettings;
Object.keys(customHeaders).forEach((key) => {
xhr.setRequestHeader(key, customHeaders[key]);
});
const headers = {
...RocketChatSettings.customHeaders,
'Content-Type': 'multipart/form-data',
'X-Auth-Token': token,
'X-User-Id': id
};
xhr.upload.onprogress = async({ total, loaded }) => {
uploadQueue[fileInfo.path] = FileUpload.fetch('POST', uploadUrl, headers, formData);
uploadQueue[fileInfo.path].uploadProgress(async(loaded, total) => {
try {
await db.action(async() => {
await uploadRecord.update((u) => {
@ -90,15 +99,14 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
} catch (e) {
log(e);
}
};
});
xhr.onload = async() => {
if (xhr.status >= 200 && xhr.status < 400) { // If response is all good...
uploadQueue[fileInfo.path].then(async(response) => {
if (response.respInfo.status >= 200 && response.respInfo.status < 400) { // If response is all good...
try {
await db.action(async() => {
await uploadRecord.destroyPermanently();
});
const response = JSON.parse(xhr.response);
resolve(response);
} catch (e) {
log(e);
@ -114,15 +122,14 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
log(e);
}
try {
const response = JSON.parse(xhr.response);
reject(response);
} catch (e) {
reject(e);
}
}
};
});
xhr.onerror = async(error) => {
uploadQueue[fileInfo.path].catch(async(error) => {
try {
await db.action(async() => {
await uploadRecord.update((u) => {
@ -133,9 +140,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
log(e);
}
reject(error);
};
xhr.send(formData);
});
} catch (e) {
log(e);
}

View File

@ -0,0 +1,22 @@
import RNFetchBlob from 'rn-fetch-blob';
class FileUpload {
fetch = (method, url, headers, data) => {
const formData = data.map((item) => {
if (item.uri) {
return {
name: item.name,
type: item.type,
filename: item.filename,
data: RNFetchBlob.wrap(decodeURI(item.uri))
};
}
return item;
});
return RNFetchBlob.fetch(method, url, headers, formData);
}
}
const fileUpload = new FileUpload();
export default fileUpload;

View File

@ -0,0 +1,48 @@
class FileUpload {
_xhr = new XMLHttpRequest();
_formData = new FormData();
fetch = (method, url, headers, data) => {
this._xhr.open(method, url);
Object.keys(headers).forEach((key) => {
this._xhr.setRequestHeader(key, headers[key]);
});
data.forEach((item) => {
if (item.uri) {
this._formData.append(item.name, {
uri: item.uri,
type: item.type,
name: item.filename
});
} else {
this._formData.append(item.name, item.data);
}
});
return this;
}
then = (callback) => {
this._xhr.onload = () => callback({ respInfo: this._xhr });
this._xhr.send(this._formData);
}
catch = (callback) => {
this._xhr.onerror = callback;
}
uploadProgress = (callback) => {
this._xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total);
}
cancel = () => {
this._xhr.abort();
return Promise.resolve();
}
}
const fileUpload = new FileUpload();
export default fileUpload;

View File

@ -74,7 +74,7 @@ class ShareListView extends React.Component {
}
const info = await Promise.all(data.filter(item => item.type === 'media').map(file => FileSystem.getInfoAsync(this.uriToPath(file.value), { size: true })));
const attachments = info.map(file => ({
filename: file.uri.substring(file.uri.lastIndexOf('/') + 1),
filename: decodeURIComponent(file.uri.substring(file.uri.lastIndexOf('/') + 1)),
description: '',
size: file.size,
mime: mime.lookup(file.uri),

View File

@ -0,0 +1,12 @@
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
index 3500542..94e45b6 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -584,6 +584,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
image.putInt("height", options.outHeight);
image.putString("mime", options.outMimeType);
image.putInt("size", (int) new File(compressedImagePath).length());
+ image.putString("filename", compressedImage.getName());
image.putString("modificationDate", String.valueOf(modificationDate));
if (includeBase64) {