Phonegap 3.0 FileTransfer download not working - javascript

I'm downloading a multi-part mime encoded image to iOS like this:
var ft = new FileTransfer();
url = encodeURI(url);
ft.download(url, path, function(fileEntry) {}, function(err) {});
with
path = "file://localhost/var/mobile/Applications/D702F059-A29F-4FF4-A165-D4A903DEDE7D/Documents/captured/2419747919.jpeg"
and get the following error:
body: "Could not create path to save downloaded file: The operation couldn’t be completed. (Cocoa error 513.)"
code: 1 (file not found)
http status: 200
This hints to an invalid path, but I can't see anything wrong with it. I get the path like this:
path = fs.root.toURL();
Everything else works fine and files can be stored in exactly the same path by taking photos. Just not via a FileTransfer download.
Any ideas or a bug in Phonegap 3.0? Thanks!
UPDATE - Workaround
FileWriter works and now even saves blobs on iOS and Android. Example code:
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
var blob = new Blob([xhr.response], {type: 'image/jpeg'});
// save via FileWriter
};
xhr.send();

I found the problem in iOS:
The path:
path = "file://localhost/var/mobile/Applications/D702F059-A29F-4FF4-A165-D4A903DEDE7D/Documents/captured/2419747919.jpeg"
does not work because it is an URL with "localhost" in it.
From FileEntry in Cordova one can get a string using "fullPath" and "toURL" ... on Android they work both to write a file.
On iOS only the fullPath works ... the URL does not successfully write a file!

I had problems with that while working on the iOS Simulator, but once I tested it on the actual device, it worked.

use nativeURL to get the prefix and append your file name to it and pass it to FileTransfer object it will work.

You'll want to use FileEntry.toURL() to get a path that looks like this:
cdvfile://localhost/persistent/path/to/file
See the documentation here: https://github.com/apache/cordova-plugin-file-transfer

Related

URL.createObjectURL() providing url that redirects to 404 in AWS production env

I have a django application deployed on AWS EBS. I have a function that takes a blob and create URL of it so that I can download the pdf file from the site. The function is working perfectly on localhost but in prod environment the created url from URL.createObjectURL() is redirecting to the error page or 404 page. Im using nginx as the reverse proxy. I have checked the blob is valid and the django function is generating the pdf layout correctly. Below is my js code to build the pdf download link
function showFile(blob){
var newBlob = new Blob([blob], {type: "application/pdf"})
if (!newBlob) {
console.error("Blob object is not valid");
return;
}
// Create a link pointing to the ObjectURL containing the blob.
const data = window.URL.createObjectURL(newBlob);
console.log(data)
var link = document.createElement('a');
link.href = data;
link.open="file.pdf";
link.download = 'Calculation.pdf';
link.click()
}
the console.log(data) is returning https://<mydomain>/d6527ea6-5c1d-457a-bfb2-2b6aff01ae31
Any idea on how I can make it work in my prod env?
Thank you
I tried to log the flow and everything is returning correctly. So I am not sure what is the issue

How to convert between MIME-Types for blob/buffer?

The Problem:
I record Audio from within the Browser which gives me a BLOB when the recording is done:
let blob = new Blob(chunks, { 'type' : 'audio/webm;codecs=opus' });
Changing the mime-type here won't help since the chunks already come with their MIME-Type which is audio/webm;codecs=opus for almost all browsers. So can not do anything here.
Sending this Blob via XHR to an node.js server will result in recieving a buffer from that blob:
Client:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:3000/audio', true);
xhr.send(blob);
Server:
app.post('/audio' , (req, res) =>{
req.on('readable',()=>{
let buffer = req.read();
// sending this buffer to the external API results in error
// since it expects the mime-type audio/wav
});
res.send({msg: 'success'});
});
Most solutions out there require you to write the file to your disk and convert that ( ffmpeg).
Others use Browser-features which are experimental or not compatible with older browsers...
I also tried using the wavfile npm package, but that creates a corrupted file if i try writing it with the UInt8Array from that webm-formatted buffer (playable file but it only contains noise and is much shorter than the actual recording should be)
There must be a simple solution to convert the binary data server side, right? Best I could wish for would be a function convertWebmBufferToWavBuffer.

Express.js: respond with image file instead of binary file?

In express, I'm trying to respond with an image to a React request, however, I get the image binary file instead of the image file.
I tried using res.sendFile and res.download, but they send the binary file. I also tried res.attachment but it works inconsistently, and somehow makes the respond pending.
I tried specifying the file type with res.type("image/jpeg"), but it doesn't make a difference.
router.get("/photo", (req, res) => {
res.type("image/jpeg");
res.sendFile("/absolute/path/image.jpg");
});
I'm expecting an image file with normal properties of name, size, etc.
I think the distinction you are drawing between an "image file" and an "image binary file" is misleading you. All bitmap image files are encoded in binary (or, on rare occasions, base64), so I don't think that gets to the root of your problem.
res.sendFile should work just fine. The problem is in your client-side JavaScript.
To display image data in the browser, you will ultimately have to use canvas or img HTML elements. The easiest way to asynchronously load an image in your app would be to update the src attribute of an img element that already exists in the DOM to the address of the image and let the browser handle the loading for you.
However, if you want to manipulate the image data prior to loading it to an img or canvas element, I would recommend using the FileReader API, as opposed to manually parsing the binary.
The key step with this approach is to set the response data type to "blob" when you make your get request
The blob data type references the binary image file, but allows you to use the browser's built-in File interface.
The code below requests an image file as a blob and then converts the blob into a base64 encoded data url that you can use as the src attribute of an img element or load to a canvas.
var xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function(event) {
fileToDataUrl(event.target.response, function(result){
console.log(result);
});
};
xhr.open('GET', "https://i.imgur.com/7VhSUEH.jpg", true);
xhr.send();
function fileToDataUrl(fileObj, callback) {
var reader = new FileReader();
reader.addEventListener("load", function() {
console.log("result")
callback(reader.result);
}, false);
reader.readAsDataURL(fileObj);
}
Try this:
const fs = require("fs");
router.get("/photo", (req, res) => {
fs.readFile("/absolute/path/image.jpg", function(err, data) {
if (err) return next(err);
res.set("Content-Type", "image/jpeg");
return res.status(200).end(data, 'binary');;
});
});
var filename = __dirname+ imagePath;
var readStream = fs.createReadStream(filename);
readStream.on('open', function () {
readStream.pipe(res);
});
readStream.on('error', function(err) {
res.end(err);
});
Please don't forget to put fs dependency

Android: URL.createObjectURL does not work properly (Failed to load because no supported source was found.)

I am trying to play an audio file that has been stored in LocalForage of my Meteor Android App.
LocalForage.getItem(track_id, (err, value)=>{
if(err)
throw err;
//the loaded value is an arraybuffer of an m4a file
let blob = new Blob([value]);
let url = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
let testAudio = new Audio(url);
testAudio.play().then(()=>{console.log("play successful")}).catch((err)=>{console.error(err)});
});
Before, I passed the url to an Instance of Howler.js, but to make it easier to comprehend what's happening, I added testAudio.
When testing in a browser (Chrome), the code works as it should. The url is created and the Audio is playing.
Android (using Chromium as all Meteor Android Apps) however, does not seem to like my approach:
When creating the Object URL, Chromium returns an URL like this:
blob:http%3A//localhost%3A12128/4de4516a-2989-4e99-9269-0beff4517fc4
As you can see, this is not a usable URL, and even if I do this
url = url.replace(/%3A/g, ':');
The resulting console output is
DOMException: Failed to load because no supported source was found.
Does anyone have an idea why this does not work on Android? I've taken a look at other questions here with the same problem, but my code looks right to me and works when tested in Chrome, so I really don't know how to approach this.
By the way, the Audiobuffer is saved like so:
const request = new window.XMLHttpRequest();
request.addEventListener('readystatechange', () => {
if (request.readyState === 4) {
LocalForage.setItem(track.file_id, request.response, (err, value) => {
console.log('Successfully saved to locale forage: ' + track.name);
})
}
});
request.open('GET', track.audioLink, true);
request.responseType = 'arraybuffer';
request.send();
I ran into something similar a while ago, and used this workaround (found in this chromium issue)
You could maybe do something like
let url = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
// workaround for mobile playback, where it didn't work on chrome/android.
// fetch blob at url using xhr, and use url generated from that blob.
// see issue: https://code.google.com/p/chromium/issues/detail?id=227476
// thanks, gbrlg
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status == 200) {
var url = (window.URL || window.webkitURL || window || {}).createObjectURL(xhr.response);
// now url is ready
}
};
xhr.send();
basically, after you create the blob url, you fetch it using XHR again, and then it works.
It's not pretty at all, but perhaps you are experiencing this bug. I used this workaround successfully in an open source project, but like I mentioned in a comment, if memory serves, it only worked for Android 5+.

Saving images from URL using JSzip

I'm using JSzip to download the html of a div. The div has images inside of it (they're not base64 encoded). Is there a way I can use JSzip to download the files from their image path url? or do they have to be base64 encoded?
My current code is just the basic demo code from the JSzip site (http://stuk.github.io/jszip/)
var zip = new JSZip();
var email = $('.Result').html();
zip.file("test.html", email);
var content = zip.generate({type:"blob"});
// see FileSaver.js
saveAs(content, "example.zip");
You might want to try using JSzip-utils it has a call just for downloading images from urls also take a look at the example in JSzip documentation I found it to be very good. You can find working example with code here.
This is just part for downloading that I'm also using to download images from social media using their image source urls.
function urlToPromise(url) {
return new Promise(function(resolve, reject) {
JSZipUtils.getBinaryContent(url, function (err, data) {
if(err) {
reject(err);
} else {
resolve(data);
}
});
});
}
var zip = new JSZip();
zip.file(filename, urlToPromise(url), {binary:true});
zip.generateAsync({type:"blob"})
.then(function callback(blob) {
// see FileSaver.js
saveAs(blob, "example.zip");
});
Here is my solution (adapted from here) building within an angular framework (though readily applicable to other frontend approaches):
NOTE: this only works if you are packaging resources -- EVEN IMAGES -- from the same origin, or that are served with 'cross-origin-resource-sharing': '*'
Make sure the JSZip UMD is included in your global namespace. In my angular case, I saved it via npm i -S jszip, and then copied the node_modules/jszip/dist/jszip.js script to my src/assets folder and included it in angular.json's scripts array.
Angular only: to get the jszip typescript definition file to work, copy node_modules/jszip/index.d.ts somewhere into src
Download npm i -S file-saver and import it as an ES6 module (see below).
Run the following function when you want the download event to occur:
import { saveAs } from 'file-saver';
async downloadData() {
// Fetch the image and parse the response stream as a blob
const imageBlob = await fetch('[YOUR CORS IMAGE URL]').then(response => response.blob());
// create a new file from the blob object
const imgData = new File([imageBlob], 'filename.jpg');
// Copy-pasted from JSZip documentation
var zip = new JSZip();
zip.file('Hello.txt', 'Hello World\n');
var img = zip.folder('images');
img.file('smile.gif', imgData, { base64: true });
zip.generateAsync({ type: 'blob' }).then(function(content) {
saveAs(content, 'example.zip');
});
}
First of all you need to download all the images with ajax. if they are on the same domain you are in luck, otherwise you need CORS or a proxy.
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(){
if (xhr.status == 200){
//Do something with xhr.response (not responseText), which should be a Blob
}
});
xhr.open('GET', 'http://target.url');
xhr.responseType = 'blob';
xhr.send(null);
When you got the image you have to manipulate the src in all <img>'s either you replace them with base64 or referring them to a folder were you have put them in a folder with JSZip
var reader = new FileReader();
reader.onload = function() {
showout.value = this.result;
};
reader.readAsDataURL(xhr.response);

Categories

Resources