60
votes

Is it possible to detect if the user is accessing through the browser or application using JavaScript?

I'm developing a hybrid application to several mobile OS through a web page and a PhoneGap application and the goal would be to:

  1. Use the same code independently of the deployment target
  2. Add PhoneGap.js file only when the user agent is an application
16

16 Answers

59
votes

You could check if the current URL contains http protocol.

var app = document.URL.indexOf( 'http://' ) === -1 && document.URL.indexOf( 'https://' ) === -1;
if ( app ) {
    // PhoneGap application
} else {
    // Web page
}
15
votes

Quick solution comes to mind is,

onDeviceReady

shall help you. As this JS call is invoked only by the Native bridge (objC or Java), the safari mobile browser will fail to detect this. So your on device app(phone gap) source base will initiate from onDeviceReady.

And if any of the Phonegap's JS calls like Device.platform or Device.name is NaN or null then its obviously a mobile web call.

Please check and let me know the results.

11
votes

I figured out a way to do this and not rely on deviceready events thus, keeping the web codebase intact...

The current problem with using the built in deviceready event, is that when the page is loaded, you have no way of telling the app: "Hey this is NOT running on an mobile device, there's no need to wait for the device to be ready to start".

1.- In the native portion of the code, for example for iOS, in MainViewController.m there's a method viewDidLoad, I am sending a javascript variable that I later check for in the web code, if that variable is around, I will wait to start the code for my page until everything is ready (for example, navigator geolocation)

Under MainViewController.m:

- (void) viewDidLoad
{
    [super viewDidLoad];
    NSString* jsString = [NSString stringWithFormat:@"isAppNative = true;"];
    [self.webView stringByEvaluatingJavaScriptFromString:jsString];
}

2.- index.html the code goes like this:

function onBodyLoad()
{
    document.addEventListener("deviceready", onDeviceReady, false);
}

function onDeviceReady(){;
    myApp.run();
}

try{
    if(isAppNative!=undefined);
}catch(err){
    $(document).ready(function(){
        myApp.run();
    });
}
7
votes

PhoneGap has window.PhoneGap (or in Cordova, it's window.cordova or window.Cordova) object set. Check whether that object exists and do the magic.

6
votes

Inside the native call where the url for the phonegap app is loaded you add a parameter target with value phonegap. So the call for android becomes something like this.

super.loadUrl("file:///android_asset/www/index.html?target=phonegap");

    var urlVars = window.location.href.split('?');
    if(urlVars.length > 1 && urlVars[1].search('target=phonegap') != -1){
        //phonegap was used for the call
        $('head').append('<script src="cordova.js"></script>');
    }
    
A small caveat
4
votes

I am using the same code for both phonegap app and our web client. Here is the code that I use to detect if phonegap is available:

window.phonegap = false;
$.getScript("cordova-1.7.0.js", function(){
    window.phonegap = true;
});

Keep in mind that phonegap js file is loaded asynchronously. You can load it synchronously by setting the correct option of a nifty jquery $.getScript function.

Note that approach does make an extra GET request to grab phonegap js file even in your webclient. In my case, it did not affect the performance of my webclient; so it ended up being a nice/clean way to do this.Well at least until someone else finds a quick one-line solution :)

4
votes

It sounds like you are loading another webpage once the webview starts in the Phonegap app, is that correct? If that's true then you could add a param to the request url based on configuration.

For example, assuming PHP,

App.Config = {
  target: "phonegap"
};

<body onload="onbodyload()">

var onbodyload = function () {
  var target = App.Config.target;
  document.location = "/home?target=" + target;
};

Then on the server side, include the phonegap js if the target is phonegap.

There is no way to detect the difference using the user agent.

4
votes

The way I'm doing it with is using a global variable that is overwritten by a browser-only version of cordova.js. In your main html file (usually index.html) I have the following scripts that are order-dependent:

    <script>
        var __cordovaRunningOnBrowser__ = false
    </script>
    <script src="cordova.js"></script> <!-- must be included after __cordovaRunningOnBrowser__ is initialized -->
    <script src="index.js"></script> <!-- must be included after cordova.js so that __cordovaRunningOnBrowser__ is set correctly -->

And inside cordova.js I have simply:

__cordovaRunningOnBrowser__ = true

When building for a mobile device, the cordova.js will not be used (and instead the platform-specific cordova.js file will be used), so this method has the benefit of being 100% correct regardless of protocols, userAgents, or library variables (which may change). There may be other things I should include in cordova.js, but I don't know what they are yet.

4
votes

Ive ben struggling with this aswell, and i know this is an old thread, but i havent seen my approach anywhere, so thought id share incase itll help someone.

i set a custom useragent after the actual useragent :

String useragent = settings.getUserAgentString();
settings.setUserAgentString(useragent + ";phonegap");

that just adds the phonegap string so other sites relying on detecting your mobile useragent still works.

Then you can load phonegap like this:

if( /phonegap/i.test(navigator.userAgent) ) 
{
//you are on a phonegap app, $getScript etc
} else {
alert("not phonegap");
}
4
votes

what if you try following :

if(window._cordovaNative) {
  alert("loading cordova");
  requirejs(["...path/to/cordova.js"], function () { 
         alert("Finished loading cordova");
  });
}
1
votes

To my mind you try to make issue for self. You didn't mentioned your development platform but most of them have different deployment configuration. You can define two configurations. And set variable that indicates in which way code was deployed. In this case you don't need to care about devices where you deployed your app.

1
votes

Short and effective:

if (document.location.protocol == 'file:') { //Phonegap is present }
1
votes

Similar to B T's solution, but simpler:

I have an empty cordova.js in my www folder, which gets overwritten by Cordova when building. Don't forget to include cordova.js before your app script file (it took my one hour to find out that I had them in wrong order...).

You can then check for the Cordova object:

document.addEventListener('DOMContentLoaded', function(){
    if (window.Cordova) {
        document.addEventListener('DeviceReady', bootstrap);
    } else {
        bootstrap();
    }
});

function bootstrap() {
   do_something()
}
0
votes

New solution:

var isPhoneGapWebView = location.href.match(/^file:/); // returns true for PhoneGap app

Old solution:
Use jQuery, run like this

$(document).ready(function(){
   alert(window.innerHeight);
});

Take iPhone as example for your mobile application,

When using PhoneGap or Cordova, you'll get 460px of WebView, but in safari, you'll lose some height because of browser's default header and footer.

If window.innerHeight is equal to 460, you can load phonegap.js, and call onDeviceReady function

0
votes

Nobody mentioned this yet, but it seems Cordova now supports adding the browser as a platform:

cordova platforms add browser

This will automatically add cordova.js during run-time, which features the onDeviceReady event, so that you do not need to fake it. Also, many plugins have browser support, so no more browser hacks in your code.

To use your app in the browser, you should use cordova run browser. If you want to deploy it, you can do so using the same commands as the other platforms.

EDIT: forgot to mention my source.

0
votes

Solution: Patch index.html in Cordova and add cordova-platform="android" to <html> tag, so that cordova-platform attribute will be only present in Cordova build and missing from original index.html used for web outside of Cordova.

Pros: Not rely on user agent, url schema or cordova API. Does not need to wait for deviceready event. Can be extended in various ways, for example cordova-platform="browser" may be included or not, in order to distinguish between web app outside of Cordova with Cordova's browser platform build.

Merge with config.xml

    <platform name="android">
        <hook src="scripts/patch-android-index.js" type="after_prepare" />
    </platform>

Add file scripts/patch-android-index.js

module.exports = function(ctx) {
    var fs = ctx.requireCordovaModule('fs');
    var path = ctx.requireCordovaModule('path');

    var platformRoot = path.join(ctx.opts.projectRoot, 'platforms/android');
    var indexPath = platformRoot + '/app/src/main/assets/www/index.html';

    var indexSource = fs.readFileSync(indexPath, 'utf-8');

    indexSource = indexSource.replace('<html', '<html cordova-platform="android"');

    fs.writeFileSync(indexPath, indexSource, 'utf-8');
}

Notes: For other than android, the paths platforms/android and /app/src/main/assets/www/index.html should be adjusted.

App can check for cordova-platform with

if (! document.documentElement.getAttribute('cordova-platform')) {
  // Not in Cordova
}

or

if (document.documentElement.getAttribute('cordova-platform') === 'android') {
  // Cordova, Android
}