Cordova FileTransfer sends file but nothing received - javascript

I'm sending a video recorded via Cordova's MediaCapture plugin to a remote server via the FileTransfer plugin, but nothing - neither the file, nor any data whatsoever - is arriving at the server end. The server receives the request, but it seems to be empty.
According to Cordova, everything goes fine. Here's the readout from the success callback:
And here's my JS: (mediaFiles[0] is the captured video file)
var options = new FileUploadOptions();
options.fileName = 'foo.bar';
options.mimeType = mediaFiles[0].type;
options.params = {
mime: mediaFiles[0].type
};
var ft = new FileTransfer();
ft.upload(
mediaFiles[0].fullPath,
encodeURI("http://xxxxxx.com/receive-video.php"),
function (r) {
console.log(r);
alert('sent file!');
},
function (error) {
alert('error');
console.log(error);
},
options,
true
);
(Note the last param, trustAllHosts, is set to true since my test server is self-signed.)
Cordova clearly thinks it's sent data, but my PHP script disagrees. Here's my PHP:
file_put_contents(
'readout.txt',
"Payload\n----------\n".
file_get_contents('php://input').
"\n\nRequest\n----------\n".
print_r($_REQUEST, 1).
"\n\nFiles\n----------\n".
print_r($_FILES, 1).
"\n\nPost\n----------\n".
print_r($_POST, 1)
);
As you can see, I'm looking pretty much everywhere. All these result in empty readouts, however, in readout.txt.
What am I doing wrong?

It turned out the chunkedMode param (in options) was the culprit.
In case this helps anyone else, disable this (it's true by default) and all should be fine.
options.chunkedMode = false;
Not sure how to explain the behaviour of the empty request with it turned on, though. The param exists to send the file in chunks, to allow for progress feedback of some sort.

Related

Implementing Dropbox API V2 in Cordova Application

I have a Cordova application with previous Dropbox implementation using rossmartin/phonegap-dropbox-sync-android. Now as the API V1 is going to be deprecated I want to upgrade to Dropbox API V2. I have searched for plugins for Cordova applications using Dropbox API V2 but didn't find any.So I am trying to implement it using dropbox/dropbox-sdk-js.
For Authentication, I am using authenticateWithCordova method which returns me the Access token (Full documentation here).This method returns Access token once the user completes authentication with Dropbox and uses the redirect URL to redirect the user to Cordova application.
This method works perfectly when the user clicks the button for the first time, but when the user clicks the button again calling this method shows a blank screen and return a new access token. How to avoid seeing the blank screen?
This is the method from Dropbox-sdk.js file, which I have called from my application,
DropboxBase.prototype.authenticateWithCordova = function (successCallback, errorCallback)
{
var redirect_url = 'https://www.dropbox.com/1/oauth2/redirect_receiver';
var url = this.getAuthenticationUrl(redirect_url);
var browser = window.open(url, '_blank');
var removed = false;
var onLoadError = function(event) {
// Try to avoid a browser crash on browser.close().
window.setTimeout(function() { browser.close() }, 10);
errorCallback();
}
var onLoadStop = function(event) {
var error_label = '&error=';
var error_index = event.url.indexOf(error_label);
if (error_index > -1) {
// Try to avoid a browser crash on browser.close().
window.setTimeout(function() { browser.close() }, 10);
errorCallback();
} else {
var access_token_label = '#access_token=';
var access_token_index = event.url.indexOf(access_token_label);
var token_type_index = event.url.indexOf('&token_type=');
if (access_token_index > -1) {
access_token_index += access_token_label.length;
// Try to avoid a browser crash on browser.close().
window.setTimeout(function() { browser.close() }, 10);
var access_token = event.url.substring(access_token_index, token_type_index);
successCallback(access_token);
}
}
};
Here is my code which I use to call the method,
function authenticateWithCordova()
{
var dbx = new Dropbox({ clientId: CLIENT_ID });
dbx.authenticateWithCordova(AuthSuccess,AuthFail);
}
function AuthSuccess(accessToken)
{
localStorage.accessToken = accessToken;
}
function AuthFail()
{
alert("Auth Fail");
}
I have found an analog issue right yesterday. This is the way I solved it.
First, I have set var dbx as global. In my index.js I put these lines immediately after app.initialize():
var CLIENT_ID = 'xxxxxxxxxxxxxxx';
var dbxt;
var dbx = new Dropbox({clientId: CLIENT_ID});
Then I check if dbxt is null: if it is, I create a new Dropbox object using accessToken, otherwise I go with the dropbox connection already established:
if (dbxt == null) {
dbx.authenticateWithCordova(function (accessToken) {
dbxt = new Dropbox({accessToken: accessToken});
dbxt.filesUpload({
path: '/mydump.sql',
contents: sql,
mode: 'overwrite',
mute: true
}).then(function (response) {
alert('Your backup has been successfully uploaded to your Dropbox!')
}).catch(function (error) {
alert('Error saving file to your Dropbox!')
console.error(error);
});
}, function (e){
console.log("failed Dropbox authentication");
}
}else{//dbxt already created
dbxt.filesUpload... //and the rest
}
This is just to avoid to create a new connection and get a new access token everytime and I confess I'm not sure this is a good practice: I only know that before to apply this code I got a lot of bad requests responses by Dropbox server:)
When I used the above code, after the first login, I started to see the blank page: that's is the inappbrowser page which Dropbox OAuth2 uses as redirect URI (set to https://www.dropbox.com/1/oauth2/redirect_receiver in your Dropbox app page).
So the problem was how to make this page invisible. I found a dirty trick applying a small tweak to inappbrowser.js script.
Near the bottom of the script, immediately before this line:
strWindowFeatures = strWindowFeatures || "";
I have put this small block:
if (strUrl.indexOf('dropbox') > -1){
strWindowFeatures += "location=no,hidden=yes";
}
I would have expected to can just use 'hidden=yes' but surprisingly if I remoce 'location=no' the blkank page appears again.
Notice 1: you don't have to modify the script inappbrowser.js located at plugins\cordova-plugin-inappbrowser\www\ but the one you find in platforms\android\platform_www\plugins\cordova-plugin-inappbrowser\www\
Notice 2: I have found this workaround right now so I'm not 100% sure it works perfectly.
Notice 3: making the inappbrowser page invisible, depending on the Internet connection, it could look like nothing is happening for a while, so you'll have to add some loader to inform your user that the app is working.
Hope this help.
UPDATE
I've just realized we can tweak directly the dropbox-sdk instead of inappbrowser.
If you are using Dropbox with browserify you have to open dropbox-base.js and look for authenticateWithCordova() method (it should be at line 107. Then change the line
var browser = window.open(url, '_blank');
to
var browser = window.open(url, '_blank', "location=no,hidden=yes");
If you are using Dropbox-sdk.min.js, you have to look for 'window.open' using the search function of your code editor. It will be easy because 'window.open' is used only once. So you'll have to change the following:
i=window.open(n,"_blank"),
to
i=window.open(n,"_blank","location=no,hidden=yes"),
And this seems to work fine (I prefer to be careful before I get excited).
UPDATE 2
Forgive previous update. My previous check:
if (strUrl.indexOf('dropbox') > -1){
strWindowFeatures += "location=no,hidden=yes";
}
is wrong because it makes invisible any inappbrowser window which tries to connect to dropbox so it prevent us from even logging into Dropbox. So we need to change it to
if (strUrl == 'https://www.dropbox.com/1/oauth2/redirect_receiver') {
strWindowFeatures += "location=no,hidden=yes";
}
This way we can do the login correctly and next connections won't show the inappbrowser window, as we want.
So summarizing:
Ignore my first update
Use UPDATE 2 to modify the url check in inappbrowser.js
Forgive me for the confusion...

Phonegap file upload fails with error 1 (file not found) on Android

I have an app with the following upload javascript:
function take_image() {
navigator.camera.getPicture(uploadPhoto, function(message) {alert('get picture failed');}, {
quality: 50,
destinationType: navigator.camera.DestinationType.FILE_URI,
sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
}
);
}
function uploadPhoto(localURI) {
window.resolveLocalFileSystemURI(localURI, function(fileEntry) {
myURI=fileEntry.fullPath;
var options = new FileUploadOptions();
options.fileKey="file";
options.fileName=myURI.substr(myURI.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
var params = new Object();
params.value1 = "test";
params.value2 = "param";
options.params = params;
options.chunkedMode = true;
var ft = new FileTransfer();
ft.upload(myURI, "http://www.mydomain.co.uk/uploadimage.php", win, fail, options);
alert(myURI + " uploading");
}, resolveFail);
}
This sits there for about 5 minutes and fails with error code 1 (file not found) from the ft.upload call.
I have already used resolveLocalFileSystemURI to get around the weird path you get from navigator.camera.getPicture sometimes, I thought that was the issue originally. The file path to upload looks good, but it comes up file not found after having a really good long think about it.
Tried everything with this, the demo code simply does not work. Could it be a permissions issue?
Sort of got to the bottom of this, I was testing this on a tablet hooked on to my wifi network. When testing on a phone using a mobile data connection it worked fine, ditto when the tablet was tethered to the phone.
No idea why it gave an error 1 (file not found) rather than an error 3 (connection error) though, or indeed why it could not use my wifi connection to send the data.
Many code examples use the code above without the resolveLocalFileSystemURI element, if you keep getting error 1 try the above.

The request is too large for IE to process properly

I am using Websync3, Javascript API, and subscribing to approximately 9 different channels on one page. Firefox and Chrome have no problems, but IE9 is throwing an alert error stating The request is too large for IE to process properly.
Unfortunately the internet has little to no information on this. So does anyone have any clues as to how to remedy this?
var client = fm.websync.client;
client.initialize({
key: '********-****-****-****-************'
});
client.connect({
autoDisconnect: true,
onStreamFailure: function(args){
alert("Stream failure");
},
stayConnected: true
});
client.subscribe({
channel: '/channel',
onSuccess: function(args) {
alert("Successfully connected to stream");
},
onFailure: function(args){
alert("Failed to connect to stream");
},
onSubscribersChange: function(args) {
var change = args.change;
for (var i = 0; i < change.clients.length; i++) {
var changeClient = change.clients[i];
// If someone subscribes to the channel
if(change.type == 'subscribe') {
// If something unsubscribes to the channel
}else{
}
}
},
onReceive: function(args){
text = args.data.text;
text = text.split("=");
text = text[1];
if(text != "status" && text != "dummytext"){
//receiveUpdates(id, serial_number, args.data.text);
var update = eval('(' + args.data.text + ')');
}
}
});
This error occurs when WebSync is using the JSON-P protocol for transfers. This is mostly just for IE, cross domain environments. Meaning websync is on a different domain than your webpage is being served from. So IE doesn't want do make regular XHR requests for security reasons.
JSON-P basically encodes the up-stream data (your 9 channel subscriptions) as a URL encoded string that is tacked onto a regular request to the server. The server is supposed to interpret that URL-encoded string and send back the response as a JavaScript block that gets executed by the page.
This works fine, except that IE also has a limit on the overall request URL for an HTTP request of roughly 2kb. So if you pack too much into a single request to WebSync you might exceed this 2kb upstream limit.
The easiest solution is to either split up your WebSync requests into small pieces (ie: subscribe to only a few channels at a time in JavaScript), or to subscribe to one "master channel" and then program a WebSync BeforeSubscribe event that watches for that channel and re-writes the subscription channel list.
I suspect because you have a key in you example source above, you are using WebSync On-Demand? If that's the case, the only way to make a BeforeSubscribe event handler is to create a WebSync proxy.
So for the moment, since everyone else is stumped by this question as well, I put a trap in my PHP to not even load this Javascript script if the browser is Internet Destroyer (uhh, I mean Internet Explorer). Maybe a solution will come in the future though.

XDomainRequest POST with XML...what am I doing wrong?

This is (hopefully) an easy question. I have to submit a request to a web service via POST with XDomainRequest. I have found sparse documentation for this across the internet, but I refuse to believe that nobody has figured this out.
Here is my XDomainRequest code:
var testURL = 'http://localhost:4989/testendpoint';
//let us check to see if the browser is ie. If it is not, let's
if ($.browser.msie && window.XDomainRequest) {
var xdr2 = new XDomainRequest();
xdr2.open("POST", testURL);
xdr2.timeout = 5000;
xdr2.onerror = function () {
alert('we had an error!');
}
xdr2.onprogress = function () {
alert('we have some progress!');
};
xdr2.onload = function () {
alert('we load the xdr!');
var xml2 = new ActiveXObject("Microsoft.XMLDOM");
xml2.async = true;
xml2.loadXML(xdr2.responseText);
};
//what form should my request take to be sending a string for a POST request?
xdr2.send("thisisastring");
}
My web service (WCF) takes a single parameter according to the web service's help page, that looks like this:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">String content</string>
I've gotten this to work via other http clients (mobile and desktop APIs, fiddler) by building a string that concatenates the parameter I am trying to pass to the web service with the rest of the string serialization. For example, I have tried:
xdr2.send("thisisastring");
xdr2.send("<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">thisisastring</string>");
but the onerror handler is always tripped. I don't think it has anything to do with the WCF because:
The WCF is always successful in every other client I call it from,
and
If it was the service, the onerror method would never get tripped.
It would return garbage, but it would be returning something.
When i use the console (in the dev tools in ie9) to log the responseText, it says:
LOG:undefined
So I am fairly sure that the issue is in how I use the XDomainRequest.
If anybody comes across this, I ended up converting my web services to return JSON-formatted data. Using JSON negates the need for XDomainRequest, allowing me to use the conventional ajax jquery tools instead.

GET HTTPs request not working properly in phonegap

I'm running this app in an Android phone (Samsung Galaxy mini running on ANdroid 2.2). I'm using couchdb for my database and host it on cloudant.
On device ready, the app will make a get request to the https db server which contains feeds of data that i need to be displayed in my app. At first, it was working fine but when i try to post and add new data to my https db server, then trigger the get request method, the returned response from the server is still the same as before(the newly posted data was not included even though i checked my db server and saw that it was indeed saved). Then i tried to close and reopen the app again, which will then make a new instance of the get http request, but still, the response still is the same as the very first one and doesnt contain the new data that was added to the db server. Then, I tried to reinstall my app, then run the app again, and oddly enough, the response from the get request now contains the new data.. I don't know how that happens, and I'm not really experienced with REST api and javascript so I might be doing something obviously wrong. Here's a snippet of my code for the get request:
var getFeedsClient;
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
if (window.XMLHttpRequest) {
getFeedsClient=new XMLHttpRequest();
}
else {
getFeedsClient=new ActiveXObject("Microsoft.XMLHTTP");
}
try{
getFeedsRequest();
} catch(e)
{
alert("Error on device ready: "+e);
}
}//on Device Ready
function getFeedsRequest() {
getFeedsClient.onreadystatechange = getFeedsFunction;
getFeedsClient.open("GET","https://<username>.cloudant.com/te/_design/requestfeed/_view/all",true);
getFeedsClient.send();
}
function getFeedsFunction() {
alert(" readystate: "+getFeedsClient.readyState+", status: "+getFeedsClient.status);
if(getFeedsClient.readyState == 4 && getFeedsClient.status == 200 ) {
var data = JSON.parse(getFeedsClient.responseText);
console.log("RESPONSE FROM COUCHDB "+getFeedsClient.responseText);
console.log("JSON data "+data);
//at first the total rows is 2 but when a new data was added, the total rows should be three, but that was not the case
alert("rows: "+data.total_rows);
}
}
I ran into a similar issue last week of the device caching requests. I solved it by adding a dummy unique parameter to the link like below:
function getFeedsRequest() {
getFeedsClient.onreadystatechange = getFeedsFunction;
var link = "https://<username>.cloudant.com/te/_design/requestfeed/_view/all";
var unique = (new Date).getTime();
getFeedsClient.open("GET",link + '?' + unique,true);
getFeedsClient.send();
}
Paul Beusterien's solution below worked for me with the following caveat:
An android 4.0.4. phone did not need a unique URL, but the same code running on 2.2.2 did!

Categories