PhoneGap app's XMLHttpRequest POST data lost - javascript

I'm porting an ajaxed, mobile-optimized website to PhoneGap, but have been unsuccessful in getting any POST to the server. From what I've read, xhreq POSTS are supposed to be possible in PhoneGap.
The specifics: I'm targeting the Android platform using the latest Cordova 3.3.1-0.1.2, the latest Android SDK, and a Galaxy S3 updated by Verizon to Android 4.3. Connectivity is over wifi to my local server. In every attempt, the POST arrives at the server as a GET, with no post data (verified using tcpdump to inspect packets). The mobile-optimized web site works fine in the browser on the same phone, also over wifi.
I've isolated the fail case by creating a brand new Phonegap project, nothing more than:
$ cordova create Hello
$ cd Hello
$ cordova platform add android
Then in index.js, at the end of the onDeviceReady handler, adding a snippet I first tested in a simple browser page (domain substituted here):
// TEST POST CAPABILITY
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState==4 && (req.status==200 || req.status==0)) {
console.log("POST Response: " + req.responseText);
}
};
var t = new Date().getTime(); // Just to foil any caching
req.open("POST", "http://mydomain.com/services/rpc?t=" + t, true); // async
req.setRequestHeader('Content-type','application/text; charset=utf-8');
var postContent = JSON.stringify({id:t, method:"misc.log", params:[{log:"POST Test"}]});
req.send(postContent);
And then run on the phone with:
$ cordova run android
It fails like the fuller app, arriving at the server as a GET with no post data. I verified a couple of configuration item defaults to make sure they were as required:
In config.xml:
<access origin="*" />
In AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Any ideas as to what might be going wrong, or other things to look in to?
Thanks.

Your content type should be set to
"application/json".
JSON.stringify() creates JSON content.
Next, can you tell us how your server process is determining the request type. Can you post the relevant code?
I would start by adjusting the content type value. See if that makes a difference.
Hope that helps.

The problem was an ip forwarding one, just not the one I'd originally suspected (forwarding to & from port 80 to my local server on port 8080, which I've used for years as a convenience to allow not having to add :8080 into the browser url all the time).
It was this:
In the MX records for "mydomain.com", I had www.mydomain.com pointing to my server's IP address, but the root mydomain.com (the host address I was using in the url to XMLHttpRequest), redirecting to www.mydomain.com.
This worked in a normal browser session, as if you type in mydomain.com, it just goes to www.mydomain.com, then runs from there - and it would use all relative paths in the xhreq's.
In PhoneGap, however, which requires the full path be specified, the POSTs were not making it through the redirect. It was also causing sluggish image loading behavior and some bizarre communication hangups after many loads - I just hadn't realized the problem had the same root cause (rather I was getting worried about WebView performance).
The great news is that POST is working fine now, and the WebView appears to be plenty speedy for my needs.
To summarize the solution: make sure that the subdomain (or lack thereof) in fully qualified urls passed to XMLHttpRequest (as required in PhoneGap) are mapped to an ip address (A record), and not redirected, in the MX records for your domain.

Related

Silent Renewal in Ionic 3 with Identity Server 4

So I am trying to implement silent renewal in an ionic 3 application, I am still learning about the whole thing so I'll try to be as descriptive as possible please correct me if I am wrong.
Authentication
I am using Implicit flow for my authentication using the In App Browser.
The user is redirected to the authentication server page
After a success authentication I retrieve the id-token & access-token
As I understand the id-token is for authentication and access-token is for authorization with the API.
I have followed this article to help me set this up (Particularly the "Deploy to Mobile Device" section) for my Identity Server.
As done in the article I am using angular-oauth2-oidc to assist me with storing information about redirect links, issuer, etc...
I have attempted to achieve this in 3 different ways, 2 of them work but I don't understand how to retrieve the new access-token and id-token, and the last one returns an error. each of them left me with questions.
First: oauthService
The angular-oauth2-oidc library has a silentRefresh() method, and this github MD describes how to use it using a hidden iframe very vaguely so I barely understand how this works. I have created a silent-refresh.html page in the same directory, but using http://localhost:8000/silent-refresh.html return's a 404. Calling the silentRefresh() method successfully re-authenticates on the server side but the request times-out on the client side, as said in the MD file, something is wrong with the iframe.
Question 1: Does the library create a new iframe and then waits for a response to the http://localhost:8000/silent-refresh.html page but is never found so I never get my response? How to proceed to retrieve my tokens?
Second: iframe
So here I follow this article where I create an iframe and add it to the body. I create my own url (similar to the one made by the silentRefresh() method), and assign it to the src of the iframe. Again on the server side everything is fine and it tries to return the tokens to http://localhost:8000.
public startRenew(url: string) {
this._sessionIframe.src = url;
return new Promise((resolve) => {
this._sessionIframe.onload = () => {
resolve();
}
});
}
Question 2: How to proceed to retrieve the tokens? as it doesn't update my tokens automatically, and I don't see how the code above can do it.
Third: In App Browser
I thought this would work fine as I already know how to process the request using the In App Browser. So I tried to use the same url that worked for the iframe in the 2nd part. However this returns an error: prompt=none was requested. But user is not authenticated. on the server side, which tells that the server can't find the session so it doesn't know who is requesting the token renewal.
Question 3: Is there a specific reason this won't work other than I made a mistake?
NOTE: Took longer than expected to write this will edit this in a bit.
oAuthService
So I looked in to the implementation of the silent refresh, to see what it does. It creates an iframe with a default id, unless you override it. That cleared up a lot of confusion as to what was actually happening.
The next mistake I did was placing the silent-refresh.html file in the src/ folder, that makes it inaccessible to the iframe. Instead the file should have been placed in the www/ folder.
Then inside the iframe I kept getting the net::ERR_CONNECTION_REFUSED error. This was due to CORS and is solved by editing the Client int the Config.cs file on the Authentication Server:
AllowedCorsOrigins = { "http://localhost:8100", "http://<ip>:8100/", "http://<ip>:8100/silent-refresh.html" },
WARNING: This didn't work once I wanted to take this outside serving in the browser (ionic serve) or emulating on my device with ionic cordova run android -c-l-s, these made the root url return something like http://<ip>/. But once ran with ionic cordova run android (without the flags), window.location.href would return file:///<example>/<something>/index.html, using this sort of path (file:///<example>/<something>/silent-refresh.html) as a redirect url caused an ERR_UNSAFE_REDIRECT error to display in the iframe.
Perhaps silentRefresh() is an Angular only solution?
In App Browser
The mistake that caused the original error was having clearsessioncache and clearcache set to yes when creating the browser, caused the session to be wiped so the authentication server didn't know who it was duh, now reduced to this:
const browser = window.cordova.InAppBrowser.open(oauthUrl, '_blank',
'location=no, hidden=yes'
);
Regular redirect url of http://localhost:8100 could be used to catch the request with the new tokens. And the silent-refresh.html page is not needed.
Here is the code for creating the oauthUrl:
buildOAuthRefreshUrl(nonce): string {
return this.oauthService.issuer + '/connect/authorize?' +
'response_type=id_token%20token' +
'&client_id=' + this.oauthService.clientId +
'&state=' + nonce +
'&redirect_uri=' + encodeURIComponent(this.oauthService.redirectUri) +
'&scope=' + encodeURI(this.oauthService.scope) +
'&nonce=' + nonce +
'&prompt=none';
}
The rest of the code is pretty much identical to the originally mentioned article

Meteor android app not showing images

I am using the excellent file-collection package,
https://atmospherejs.com/vsivsi/file-collection
to store images in my Mongo database. Running the app on Android doesn't show the images (they appear as broken images). In the browser it is perfect.
I don't think the problem is unique to this package, as it is using Mongo's gridfs to store the images, and provides URL's to access them.
Here is a note from Vaughn in the documentation:
Cordova Android Bug with Meteor 1.2+
Due to a bug in the Cordova Android version that is used with Meteor
1.2, you will need to add the following to your mobile-config.js or you will have problems with this package on Android devices:
App.accessRule("blob:*");
Which I have done, but without success.
I also see the documentation references setting headers to deal with CORS issues, like this:
myFiles = new FileCollection('myFiles',
{ resumable: true, // Enable built-in resumable.js chunked upload support
http: [ // Define HTTP route
{ method: 'get', // Enable a GET endpoint
path: '/:md5', // this will be at route "/gridfs/myFiles/:md5"
lookup: function (params, query) { // uses express style url params
return { md5: params.md5 }; // a query mapping url to myFiles
},
handler: function (req, res, next) {
if (req.headers && req.headers.origin) {
res.setHeader('Access-Control-Allow-Origin', 'http://meteor.local'); // For Cordova
res.setHeader('Access-Control-Allow-Credentials', true);
}
next();
}
},
But again without success.
Looking at the network tab on the inspector, I can't even see requests for the images from the server, which suggests that it is being denied by something in the Cordova code, and it's not even trying to go out and get the images.
I have reproduced the problem using Vaughn's demo app, which I have forked and added the android platform, so it's ready to go if you care to try and help.
https://github.com/mikkelking/meteor-file-sample-app
If you do a meteor run android-device it should run on the Android. You will need to register and then upload an image to see the problem. From a browser it works fine.
Any help would be appreciated, this is a show stopper for my project. One option I have considered is to move the images to an S3 bucket, which I think should work, but I'd like to keep the images in the db if I can.
I had a similar issue once with gridfs. I believe that the issue comes because the image source is a relative source. So your image sources are coming from localhost. It works on the web version because the browser is on the same machine as your server, so a localhost source works fine. But on the android device it won't work because the images are not served on that device.
When I had this problem I just deployed to production and it worked on mobile devices because the image source pointed to a url that was on the internet and not relative to the device. This works for production but not for dev testing.
When I saw this question I cloned your code and got it working on an android device for local dev.
The first step I did is to set the ROOT_URL env variable and mobile server to point to the your local server. When you run meteor locally you can run a command like this to set these variables, using your computer's local ip address
export ROOT_URL=http://192.168.1.255:3000 && meteor run android-device --mobile-server=http://192.168.1.255:3000
Next, in your sample.coffee Template.collTest.helpers link function, you need to use the absolute url instead of a relative one (so that on your mobile device it will look to your local server instead of localhost). To dynamically get this so that it works on different servers, you can use something like this
Meteor.absoluteUrl(myData.baseURL + "/md5/" + this.md5)
Then I had to add the computer's ip address http://192.168.1.255:3000 to the content security policies in the sample.jade file.
I almost forgot, at this point I was getting a 403 forbidden error. I changed the myData.allow read function in sample.coffee and just returned true and the 403 was gone, something was happening with the permissions there
After that the image showed up on my android device.

Chrome extensions for silent print?

I have made a silent print web application that prints a PDF file. The key was to add JavaScript to the PDF file that silently print itself.
To do this I open the PDF with acrobat reader in chrome, that allow me to execute the script (with the proper permissions).
But as it was announced this solution won't work after chrome 45 because the npapi issue.
I guess a possible solution could be to use the recently release printProvider of chrome extensions.
Nevertheless I can't imagine how to fire any of the printProvider events.
So the question is: Is ok to think in chrome extensions to make a silent print web application, and how can I fire and handle a print job for an embedded PDF of a HTML Page.
Finally I reached an acceptable solution for this problem, as I couldn't find it out there, but read to many post with the same issue I will leave my solution here.
So first you need to add your printer to the Google Cloud Print and then you will need to add a proyect to the Google Developers Console
Then add this script and any time you need to print something execute the print() function. This method will print the document indicated in the content
The application will ask for your permission once to manage your printers.
function auth() {
gapi.auth.authorize({
'client_id': 'YOUR_GOOGLE_API_CLIENT_ID',
'scope': 'https://www.googleapis.com/auth/cloudprint',
'immediate': true
});
}
function print() {
var xhr = new XMLHttpRequest();
var q = new FormData()
q.append('xsrf', gapi.auth.getToken().access_token);
q.append('printerid', 'YOUR_GOOGLE_CLOUD_PRINTER_ID');
q.append('jobid', '');
q.append('title', 'silentPrintTest');
q.append('contentType', 'url');
q.append('content',"http://www.pdf995.com/samples/pdf.pdf");
q.append('ticket', '{ "version": "1.0", "print": {}}');
xhr.open('POST', 'https://www.google.com/cloudprint/submit');
xhr.setRequestHeader('Authorization', 'Bearer ' + gapi.auth.getToken().access_token);
xhr.onload = function () {
try {
var r = JSON.parse(xhr.responseText);
console.log(r.message)
} catch (e) {
console.log(xhr.responseText)
}
}
xhr.send(q)
}
window.addEventListener('load', auth);
<script src="https://apis.google.com/js/client.js"></script>
Anyway this script throw a 'Access-Control-Allow-Origin' error, even though this appears in the documentation... I couldn't make it work :(
Google APIs support requests and responses using Cross-origin Resource Sharing (CORS). You do not need to load the complete JavaScript client library to use CORS. If you want your application to access a user's personal information, however, it must still work with Google's OAuth 2.0 mechanism. To make this possible, Google provides the standalone auth client — a subset of the JavaScript client.
So to go throw this I had to install this chrome extension CORS. I'm sure that some one will improve this script to avoid this chrome extension.
You can register an Application to a URI Scheme to trigger the local application to print silently. The setting is pretty easy and straightforward. It's a seamless experience. I have posted the solution here with full example:
https://stackoverflow.com/a/37601807/409319
After the removal of npapi, I don't believe this is possible solely programmatically. The only current way I know to get chrome to print silently is using chrome kiosk mode, which is a flag (mode) you have to set when starting chrome.
Take a look at these SO posts:
Silent printing (direct) using KIOSK mode in Google Chrome
Running Chrome with extension in kiosk mode
This used to be possible using browser plugins (e.g. Java + NPAPI, ActiveX) but has been blacklisted by most browsers for several years.
If interested in modern solutions that use similar techniques, the architecture usually requires the following:
WebSocket, HTTP or Custom URI connection back to localhost
API that talks through web transport (JavaScript or custom URI scheme) to an app running locally.
A detail of projects (several of them are open source) that leverage these technologies are available here:
https://stackoverflow.com/a/28783269/3196753
Since the source code of these projects can vary (hundreds of lines to tens-of-thousands of lines), a code snippet would be too large unless a inquiring about a specific project's API.
Side note: Some technologies offer dedicated cloud resources, which add convenience at the expense of potential latency and privacy. At the time of writing this, the most popular "free" cloud solution -- Google Cloud Print -- is slated to be retired in December 2020.

Why do I have differences with AJAX hosted local vs remote?

I guess part of my answer relates to "same-origin" but I'm not still not absolutely clear on when it applies and when not (or why it works in one instance, but is not a solution in other cases).
I am using latest jQuery, jQuery mobile and Apache/MySQL/PHP stacks. Client is either Windows 7/Firefox 38, or iPad/PhoneGap.
My AWS hosted php code serves the following to help resolve "same origin":
$http_origin = $_SERVER['HTTP_ORIGIN'];
header("Access-Control-Allow-Origin: $http_origin");
The Phone Gap version of my app works as expected, retrieving data from my AWS server and rendering the data it has retrieved.
Calling the exact same code from my laptop browser fails. Why?
If I copy/paste the AJAX URL into my browser, it correctly pulls the JSON data from AWS.
In an effort to resolve, I dump output to console.log. The jQuery AJAX "error" section gets called instead of the "success" portion. The same "error" result occurs if I call the index.html file (which calls JS) using File Open within Firefox, or if I call the locally apache hosted index.html file, jQuery ajax jumps to "error" section.
So if my phonegap app works, but my laptop does not, why? I mean, I see my app being akin to the laptop web browser. They both in effect have a different origin than my web server so I would expect either both work, or both fail.
If someone can help clarify it would be great - I have twice spent time chasing a problem that only exists in my dev environment but works just fine in production - its frustrating!
Thanks all in advance
Have you white listed domain in your phonegap config ?
http://docs.phonegap.com/en/4.0.0/guide_appdev_whitelist_index.md.html
ex:
Access to google.com:
<access origin="http://google.com" />
__ reading it should improve : https://github.com/phonegap/phonegap-app-developer/issues/169 __

API calls are not working in cordova application

I made an application using apache cordova. my server is written in node.js running locally and port 3005. So I want to make API call from cordova I am using backbone in client side.
I written the following code to make API call
makingAPICallForStatus:function(){
var userSessionModel = Backbone.Model.extend({ //Creaating a model for checking user session status
url:"http://localhost:3005/api/user/status/",
});
var userSessionModelObj=new userSessionModel();
this.makeApiCall(userSessionModelObj,"",'GET',function(model,response,options){console.log(response);});
},
makeApiCall:function(modelObj,dataObject,requestType,successCallback){
modelObj.fetch({data:dataObject,
type:requestType,
success:successCallback,
error:function(){console.log("error")}
});
}
If you observe, I mention URL path is :"http://localhost:3005/api/user/status/". This way it's not working. it's showing This request has no response data available
Now I tried with production domain like
URL path is :"http://xxx.xxxx.com/api/user/status/"
This way it's working fine.
Why localhost was not working, I invoke same url directly in my browser working fine. but it's not working in cordova.
Note : I didn't modify anything in www/config.xml file.
What's the problem how can I fix this.
Thanks.
localhost is an alias of 127.0.0.1 even if you are running on an emulator on the same machine, local host is not real. You have to always make calls to an ipaddress or domain name from a device (even virtual devices). This is because on the device localhost is referring to the device not the machine that it may be running from.

Categories

Resources