Cordova file upload - javascript

I want to upload a video to YouTube from my phone device storage. However when I upload the file it comes through as blank. When I use the same upload code but with a web file, it works. Wondering where I am going wrong!
Method one
everything uploads correctly and the video plays on YouTube.
loadWebFile('assets/intro.mpg');
function loadWebFile(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = function (e) {
uploadFile(xhr.response); // type: Blob
};
xhr.onerror = function (e) {
console.log('loadWebFile.onerror', e);
};
xhr.send();
};
Method two
The video title and description appears on YouTube, but the video is blank. I'm definitely passing through a valid file.
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
if (window.webkitStorageInfo) {
window.webkitStorageInfo.requestQuota(access, 1024 * 1024, function (bytes) {
if (window.requestFileSystem) {
window.requestFileSystem(access, bytes, function (filesystem) {
loadFile('/Movies/intro.mpg');
}, me.onError);
} else {
window.alert('requestFileSystem not supported');
}
}, me.onError);
} else {
window.alert('webkitStorageInfo not supported');
}
// this sends an empty video to YouTube
function loadFile(path) {
filesystem.root.getFile(path, null, function (fileEntry) {
fileEntry.file(function (file) {
uploadFile(file); // type: File
});
}, function (e) {
console.log('loadFile.error', e);
});
}
Both methods share the same upload function:
// uploads using the YouTube script
// https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js
function uploadFile(file) {
var metadata = {
snippet: {
title: 'Video title',
description: 'Video description',
tags: 'Video tags',
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
};
var uploader = new MediaUploader({
baseUrl: 'https://www.googleapis.com/upload/youtube/v3/videos',
file: file,
token: accessToken,
metadata: metadata,
params: {
part: Object.keys(metadata).join(',')
},
onError: function (e) {
console.log('onError', JSON.parse(e));
},
onProgress: function (e) {
console.log('onProgress', e);
},
onComplete: function (e) {
console.log('onComplete', JSON.parse(e));
}
});
uploader.upload();
};
I've have an example project with some of the code (minus the upload script) here:
https://github.com/kmturley/cordova-files

Here another working solution. I tested right now and it does work. You need standard cordova-plugin-file
function uploadFileToServer (fileUri, fileName, remoteUrl, callback) {
window.resolveLocalFileSystemURL(fileUri, function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader()
reader.onloadend = function () {
var blob = new Blob([new Uint8Array(this.result)], { type: 'application/octet-stream' })
var fd = new FormData()
fd.append('file', blob, fileName)
var xhr = new XMLHttpRequest()
xhr.open('POST', remoteUrl, true)
xhr.onload = function () {
if (xhr.status === 200) {
if (typeof callback === 'function') { callback() }
} else {
if (typeof callback === 'function') { callback(xhr.status) }
}
}
xhr.onerror = function (err) {
if (typeof callback === 'function') { callback(err) }
}
xhr.send(fd)
}
reader.readAsArrayBuffer(file)
}, function (err) {
if (typeof callback === 'function') { callback(err) }
})
})
}
You call it:
uploadFileToServer('file:///storage/emulated/0/Android/data/myfile.jpg',
'myfile.jpg',
'https://example.com/upload_url',
(err) => {
if (err) {
console.error('Error uploading file', err)
} else {
console.log('Upload done it with success')
}
})

The oficial file-transfer plugin is deprecated and when I'm writing this, its test script fails.
This made me use a pure javascript approach and it does work
function uploadFile (localPath, fileName, remoteUrl, callback) {
// loads local file with http GET request
var xhrLocal = new XMLHttpRequest()
xhrLocal.open('get', localPath)
xhrLocal.responseType = 'blob'
xhrLocal.onerror = () => {
callback(Error('An error ocurred getting localpath on' + localPath))
}
xhrLocal.onload = () => {
// when data is loaded creates a file reader to read data
var fr = new FileReader()
fr.onload = function (e) {
// fetch the data and accept the blob
console.log(e)
fetch(e.target.result)
.then(res => res.blob())
.then((res) => {
// now creates another http post request to upload the file
var formData = new FormData()
formData.append('imagefile', res, fileName)
// post form data
const xhrRemote = new XMLHttpRequest()
xhrRemote.responseType = 'json'
// log response
xhrRemote.onerror = () => {
callback(Error('An error ocurred uploading the file to ' + remoteUrl))
}
xhrRemote.onload = () => {
if (typeof callback === 'function') {
callback(null, 'File uploaded successful, ' + xhrRemote.response)
}
}
// create and send the reqeust
xhrRemote.open('POST', remoteUrl)
xhrRemote.send(formData)
})
}
fr.readAsDataURL(xhrLocal.response) // async call
}
xhrLocal.send()
}
Now just call it something like this
uploadFile('file:///storage/emulated/0/Android/data/myfile.jpg',
'myfile.jpg',
'https://example.com/upload_url',
(err, res) => {
if (err) {
console.error(err)
} else {
console.log(res)
}
})

So to upload files I realised for:
web files use a CORS upload:
https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js
local device files us the cordova file transfer plugin:
https://github.com/apache/cordova-plugin-file-transfer
The code i'm now using for local files which uploads the file and sets correct metadata:
function uploadVideo(fileURL) {
var options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
options.mimeType = 'video/mpg';
options.chunkedMode = false;
options.headers = {
Authorization: 'Bearer ' + accessToken
};
options.params = {
"": {
snippet: {
title: 'Video title',
description: 'Video description',
tags: 'Video tags',
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
}
};
var ft = new FileTransfer();
ft.upload(fileURL, 'https://www.googleapis.com/upload/youtube/v3/videos?part=snippet,status', function (data) {
console.log('upload success', data);
}, function (e) {
console.log('upload error', e);
}, options, true);
ft.onprogress = function (progressEvent) {
console.log('onprogress: ' + ((progressEvent.loaded / progressEvent.total) * 100) + '%');
};
}
And I also had to modify the plugin to allow the metadata to be passed through to YouTube using a single request:
FileTransfer.java
lines 374 - 376
beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append("\";");
beforeData.append(" filename=\"").append("file.json").append('"').append(LINE_END);
beforeData.append("Content-Type: ").append("application/json").append(LINE_END).append(LINE_END);
If you do modify the plugin, remember cordova caches this code. I use this command to force it to update the plugin:
cordova platform remove android; cordova platform add android;

Related

React Native: Retrieve image URL from firestore

This is a function that uploads an image to the firebase storage and then retrieves the URL using the 'getDownloadURL' function.
The uploading of images works fine but it fails to retrieve the URL as it is trying to access the URL while the image is still uploading.
Please solve this problem !!
const getGSTURI = async () => {
if (GSTLoading) {
return;
}
setGSTLoading(true);
const result = await DocumentPicker.getDocumentAsync({
copyToCacheDirectory: true,
});
console.warn(result);
setGSTName(result.name);
setGSTURI(result.uri);
setGSTLoading(false);
async function uploadGST(uri, name) {
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
resolve(xhr.response);
};
xhr.onerror = function (e) {
console.warn(e);
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
const storageRef = ref(storage, `sellers/${sellerID}/${name}`);
uploadBytes(storageRef, blob).then(() => {
console.warn("GST Upload Successfull");
});
getDownloadURL(ref(storage, `sellers/${sellerID}/${name}`))
.then((url) => {
// `url` is the download URL for 'images ' stored in firestorage
console.log(url);
setGSTURL(url);
console.log(GSTURL);
})
.catch((error) => {
"Errors while downloading";
});
// We're done with the blob, close and release it
blob.close();
}
uploadGST(GSTURI, GSTName);
};
you have to wait for the uploadBytes function to complete before trying to retrieve the url
instead of
uploadBytes(storageRef, blob).then(() => {
console.warn("GST Upload Successfull");
});
you can use the await operator as below to wait for the task to complete
try {
await uploadBytes(storageRef, blob);
console.warn('GST Upload Successfull');
} catch (e) {
console.warn('GST Upload Failed', e);
}

Image cannot be uploaded to S3 due to base 64 encoding

I am trying to code a functionality in my website which allows users to update their profile picture. The S3 url updates in dynamoDB however the image does not get uploaded to S3 and I do not understand why.
I believe I have taken the necessary steps to decode the base 64 image in lambda. However it still does not upload the image to S3. Any suggestions will be helpful. The image I tried to upload is a png. In console response.message is "file_key and file URL updated" but file is not uploaded to S3.
Javascript:
function uploadImage() {
var response = "";
var jsonData = new Object();
jsonData.user_name = sessionStorage.getItem("user_name");
var file = document.getElementById("imageUploader").files[0];
if (!file) {
alert("No file selected")
throw new Error("No file selected.");
}
var reader = new FileReader();
reader.readAsDataURL(file)
reader.onload = function () {
console.log(file)
jsonData.file = file.name;
jsonData.file_key = reader.result.split(',')[1];
console.log(reader.result)
console.log(jsonData.file_key)
// console.log(jsonData.file_key)
var request = new XMLHttpRequest();
request.open("PUT", "https://acdn3jddd.execute-api.us-east-1.amazonaws.com/user-profile-image", true);
request.onload = function () {
if (request.status === 200) {
response = JSON.parse(request.responseText);
console.log(response);
console.log(response.message);
if (response.message == "file_key and file URL updated") {
alert('Image uploaded successfully.');
} else {
alert('Error. Unable to upload image.');
}
} else {
console.error(`Request failed with status: ${request.status}`);
}
};
request.send(JSON.stringify(jsonData));
};
}
Lambda code:
case 'PUT /user-profile-image':
body = JSON.parse(event.body);
const fileKey = body.file_key;
const userName = body.user_name;
const s3Params = {
"Bucket": 'uploading-image-bucket',
"Key": body.file_key,
"Body": fileKey,
"ContentType": 'image/png'
};
s3.upload(s3Params, (err, data) => {
if (err) {
return callback(err);
}
// Get URL of the uploaded file
const fileUrl = `https://s3.amazonaws.com/${s3Params.Bucket}/${fileKey}`;
// Store URL in DynamoDB
const dynamoParams = {
TableName: 'hearty_eats_users',
Key: { "user_name": userName },
UpdateExpression: "set file_key = :file_key",
ExpressionAttributeValues: {
":file_key": fileUrl,
},
};
dynamo.update(dynamoParams, (err, result) => {
if (err) {
return callback(err);
}
return callback(null, { "message": "file_key and file URL updated" });
});
});

What is analog of URL.createObjectURL for Service Worker

With manifest v2, I used URL.createObjectURL in order to return file from background to content. How do this with manifest v3 and Service Worker? I know about XMLHttpRequest to fetch. But what analog URL.createObjectURL?
// background.js
function onContentMessage(message, sender, sendResponse) {
if (message.action == 'requestFileGET') {
requestFileGET(message.url, (url) => sendResponse(url));
}
return true;
}
function requestFileGET(url, callback) {
let request = new XMLHttpRequest();
request.responseType = 'blob';
request.onreadystatechange = function () {
if (request.readyState == 4) {
let url = URL.createObjectURL(request.response);
callback(url);
}
};
request.open('GET', url);
request.send();
}
// content.js
backgroundFileGET(urlCover, (cover) => {
let url = `${HANDLER_UPLOADPIC}?kind=${kind}&sign=${sign}`;
let formData = new FormData();
formData.append('file', cover);
requestFilePOST(url, formData, callback);
});
function backgroundFileGET(url, callback) {
backgroundRequest('requestFileGET', url, (backgroungdUrl) => {
requestFileGET(backgroungdUrl, (file) => callback(file));
});
}
function backgroundRequest(action, url, callback) {
chrome.runtime.sendMessage({ action: action, url: url }, (response) => callback(response));
}
My Solution Store the data with a ID and open a new tab with a page like download.html#ID
let videoId = '#' + new Date().valueOf();
self.DownloadedVideoData[videoId] = { data: data, filename:
filename, mimetype:mimetype, segment:segment, action:'download' };
chrome.tabs.create({ url: "video.html" + videoId, active: false });
Inside the Page register a function to onload and your downloading Code
chrome.runtime.sendMessage({
action: 'GetVideoDataByID',
VideoID: self.VideoID,
}, function (videoData) {
if (videoData.action == 'download') self.DownloadVideo(videoData);
);
self.DownloadVideo = function (request) {
let blob = new Blob(request.data, { type: request.mimetype }),
downloadId;
chrome.downloads.onChanged.addListener(function (dl) {
if (dl.id === downloadId && dl.state && dl.state.current ==
'complete') {
window.close();
}
});
chrome.downloads.download({
url: URL.createObjectURL(blob),
filename: request.filename
}, function (id) {
downloadId = id;
window.close();
});
}
Now you need only a Listener in your Backround Worker like that
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.action) {
case 'GetVideoDataByID':
sendResponse(self.GetVideoDataByID(request.VideoID));
break;
default:
break;
}
}

GET request from browser works to download file to local but XMLHttpRequest Javascript script does not download file

I'm having trouble with XMLHttpRequest I think, when I navigate to localhost/dashboard/downloadfile?file-name=hw3.txt the file downloads locally but If I use the function checkDownload() to start an XMLHttpRequest the file does not get downloaded.
Here is my client code:
function checkDownload() {
const filename = "hw3.txt";
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open('GET', `/dashboard/downloadfile?file-name=${ filename }`);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
}
}
}
xhr.send();
}
And then here is my server code:
app.get('/dashboard/downloadfile', requiresLogin, (req, res) => {
const userid = req.user.id;
const filename = req.query['file-name'];
db.getFileKey([userid, filename], (keyres) => {
const params = {
Bucket: S3_BUCKET,
Key: keyres.rows[0].filekey,
};
res.setHeader('Content-disposition', `attachment; filename=${ filename }`);
res.setHeader('Content-type', `${ mime.getType(keyres.rows[0].filetype) }`);
s3.getObject(params, (awserr, awsres) => {
if(awserr) console.log(awserr);
else console.log(awsres);
}).createReadStream().pipe(res);
});
});
I got it working. Instead of trying to create a read stream from s3.getObject() I generated a signed url to the s3 object on the server and returned that to the client, then used an 'a' html element with element.href = signedRequest and used javascript to click that element. The new problem I'm running into is that I can't figure out a way to set the metadata for the s3 object when it is initially uploaded, I needed to manually change the metadata on an individual s3 object through the aws console so that it had the header Content-Disposition: attachment; filename=${ filename }.
changed client code:
function initDownload(filename) {
const xhr = new XMLHttpRequest();
xhr.open('GET', `/sign-s3-get-request?file-name=${ filename }`);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
startDownload(response.signedRequest, response.url);
}
}
}
xhr.send();
}
function startDownload(signedRequest, url) {
var link = document.createElement('a');
link.href = signedRequest;
link.setAttribute('download', 'download');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
changed server code:
app.get('/sign-s3-get-request', requiresLogin, (req, res) => {
const userid = req.user.id;
const filename = req.query['file-name'];
db.getFileKey([userid, filename], (keyres) => {
const s3Params = {
Bucket: S3_BUCKET,
Key: keyres.rows[0].filekey,
Expires: 60,
};
s3.getSignedUrl('getObject', s3Params, (err, data) => {
if (err) {
// eslint-disable-next-line
console.log(err);
res.end();
}
const returnData = {
signedRequest: data,
url: `https://${S3_BUCKET}.s3.amazonaws.com/${ keyres.rows[0].filekey }`,
};
res.write(JSON.stringify(returnData));
res.end();
});
});
});
You are getting a blob back from the server, so in order to download you need to do something when xhr.status === 200.
Something like this:
...
if(xhr.status === 200) {
var fileUrl = URL.createObjectURL(xhr.responseText)
window.location.replace(fileUrl)
}
...
To download having the URL you could use the attribute download of a tag:
<a download="something.txt" href="https://google.com">Download Google</a>
If you use xhr.responseType = "blob", you have to do somethig like:
function checkDownload() {
const filename = "hw3.txt";
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
var reader = new FileReader();
reader.readAsArrayBuffer(xhr.response);
reader.addEventListener("loadend", function() {
var a = new Int8Array(reader.result);
console.log(JSON.stringify(a, null, ' '));
});
}
}
}
xhr.send();
}
checkDownload()
But that code doesn't download the file.

How to download any file from Byte Array in Cordova application?

I have a cordova - ionic application.
I want to download a file from webservice and the file may be any type(JPG,PDG,DOCX etc). I cannot download the file from direct URL. So the app is taking byte array of the file from Webservice.
Anybody know how to download the file in Mobile from the Byte Array. Please help me.
you can use the
cordova-plugin-file and the
cordova-plugin-file-opener2 plugin.
https://github.com/apache/cordova-plugin-file
https://github.com/pwlin/cordova-plugin-file-opener2
in function with the webservice your code should look like that:
var bytes = new Uint8Array(data.d);
app.writePDFToFile(fileName.split(fileName.split, bytes);
and here is teh function forcing the download:
writePDFToFile: function (fileName, data) {
try {
window.resolveLocalFileSystemURL(cordova.file.externalApplicationStorageDirectory, function (directoryEntry) {
directoryEntry.getFile(fileName, { create: true }, function (fileEntry) {
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function (e) {
//window.open(cordova.file.externalApplicationStorageDirectory + fileName, '_system', 'location=yes');
cordova.plugins.fileOpener2.open(cordova.file.externalApplicationStorageDirectory + fileName, 'application/pdf',
{
error: function (e) {
console.log('Error status: ' + e.status + ' - Error message: ' + e.message);
},
success: function () {
console.log('file opened successfully');
}
}
);
};
fileWriter.onerror = function (e) {
alert(e);
};
var blob = new Blob([data], { type: 'application/pdf' });
fileWriter.write(blob);
}, function onerror(e) {
alert(e);
});
}, function onerror(e) {
alert(e);
});
}, function onerror(e) {
alert(e);
});
} catch (e) {
alert(e);
}
},
Hope this will help!

Categories