120
votes

I'm developing a web application that uses PhoneGap:Build for a mobile version and want to have a single codebase for the 'desktop' and mobile versions. I want to be able to detect if PhoneGap calls will work (ie, is the user on a mobile device that will support PhoneGap).

I've searched and cannot believe there is no simple way of doing this. Many people have offered suggestions;

None of which work, unless you remove the PhoneGap Javascript file from the desktop version of the app, which defeats my goal of having one codebase.

So far the only solution I have come up with is browser / user agent sniffing, but this is not robust to say the least. Any better solutions welcome!

EDIT: A marginally better solution is to try calling a PhoneGap function after some small timeout - if it doesn't work, then assume the user is on a desktop web browser.

30
Since you're using Build, see @b-t's answer below: stackoverflow.com/a/18478002/241244 . Seems like it might be better than the accepted and top-voted answers.user241244
I avoid run-time detection in favor of explicit build-time configuration because it's 100% effective. I simply pass a local var to my index.jade template like {isPhonegap: true}, then in the template I can conditionally include the phonegap.js script, and perform all the phonegap specific init I want.Jesse Hattabaugh

30 Answers

116
votes

I use this code:

if (navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/)) {
  document.addEventListener("deviceready", onDeviceReady, false);
} else {
  onDeviceReady(); //this is the browser
}

UPDATE

There are many other ways to detect if phonegap is running on a browser or not, here is another great option:

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

as seen here: Detect between a mobile browser or a PhoneGap application

49
votes

I wrote a post about it a few days ago. This is the best solution you can find (until PhoneGap will release something, maybe or maybe not), it's short, simple and perfect (I've checked it in every possible way and platform).

This function will do the job for 98% of the cases.

/**
 * Determine whether the file loaded from PhoneGap or not
 */
function isPhoneGap() {
    return (window.cordova || window.PhoneGap || window.phonegap) 
    && /^file:\/{3}[^\/]/i.test(window.location.href) 
    && /ios|iphone|ipod|ipad|android/i.test(navigator.userAgent);
}

if ( isPhoneGap() ) {
    alert("Running on PhoneGap!");
} else {
    alert("Not running on PhoneGap!");
}

To complete the other 2% of the cases, follow these steps (it involves a slight change on native code):

Create a file called __phonegap_index.html, with the source:

<!-- __phonegap_index.html -->
<script type="text/javascript">
    function isPhoneGap() {
        //the function's content is as described above
    }

    //ensure the 98% that this file is called from PhoneGap.
    //in case somebody accessed this file directly from the browser.
    if ( isPhoneGap() )
        localStorage.setItem("isPhoneGap","1");

    //and redirect to the main site file.
    window.location = "index.html";
</script>

Now, on native simply change the start page from index.html to __phonegap_index.html on all your PhoneGap platforms. Let's say my project name is example, the files you need to change are (as for PhoneGap version 2.2.0):

  • iOS - CordovaLibApp/AppDelegate.m
  • Android - src/org/apache/cordova/example/cordovaExample.java
  • Windows 8 - example/package.appxmanifest
  • BlackBerry - www/config.xml
  • WebOS - framework/appinfo.json
  • Bada - src/WebForm.cpp (line 56)
  • Window Phone 7 - No idea where (somebody still developing on that platform?!)

Finally, you can use it anywhere on your site, if it's running on PhoneGap or not:

if ( localStorage.getItem("isPhoneGap") ) {
    alert("Running on PhoneGap!");
} else {
    alert("Not running on PhoneGap!");
}

Hope it helps. :-)

27
votes

I know it's been answered a while ago but "PhoneGap.available" doesn't exist anymore. You should use:

if (window.PhoneGap) {
  //do stuff
}

or since 1.7, prefer:

if (window.cordova) {
  //do stuff
}

EDIT 2019: as said in the comments, this only works if you do not include cordova lib into your desktop browser build. And of course it is a good practice to include only the strict minimum javascript/html/css files for each device you target

22
votes

The most trustable way we found to tell if we are in a cordova/phonegap application is to modify the cordova application's user agent using this config AppendUserAgent.

In config.xml add:

<preference name="AppendUserAgent" value="Cordova" />

Then call:

var isCordova = navigator.userAgent.match(/Cordova/i))

Why?

  1. window.cordova and document.addEventListener('deviceready', function(){}); are subject to racing conditions
  2. navigator.standalone does not work when <content src="index.html" /> is a website (Ex: <content src="https://www.example.com/index.html" /> or with cordova-plugin-remote-injection)
  3. Trying to whitelist user agents to guess if it is a real browser is very complicated. Android browsers are often custom webviews.
14
votes

I think this is simplest: var isPhoneGap = (location.protocol == "file:")

EDIT For some people that didn't work. Then you might try (haven't tested)

var isPhoneGap = ! /^http/.test(location.protocol);
8
votes

This works for me (running 1.7.0)

if (window.device) {
  // Running on PhoneGap
}

Tested on desktop Chrome and Safari.

7
votes

Like the original poster, I'm using the phonegap build service. After two days and nearly 50 test builds, I've come up with an elegant solution that works great for me.

I couldn't use UA sniffing because I wanted to test and run in mobile browsers. I had originally settled on cobberboy's quite functional technique. This didn't work for me because the "howPatientAreWe: 10000" delay/timeout was too much of a nuisance for in-browser development. And setting it any lower would occasionally fail the test in app/device mode. There had to be another way...

The phonegap build service requires the phonegap.js file be omitted from your code repository before submitting your app's files to the service. Therefore I'm able to test for its existence to determine if running in a browser vs. app.

One other caveat, I'm also using jQueryMobile, so both jQM and phonegap had to initialize before I could begin any custom scripting. The following code is placed at the beginning of my custom index.js file for the app (after jQuery, before jQM). Also the phonegap build docs say to place <script src="phonegap.js"></script> somewhere in the HTML. I leave it out completely and load it using $.getScript() to facility testing its existence.

isPhoneGap = false;
isPhoneGapReady = false;
isjQMReady = false;

$.getScript("phonegap.js")
.done(function () {
    isPhoneGap = true;
    document.addEventListener("deviceready", function () {
        console.log("phonegap ready - device/app mode");
        isPhoneGapReady = true;
        Application.checkReadyState();
    }, false);
})
.fail(function () {
    console.log("phonegap load failed - browser only");
    isPhoneGapReady = true;
    Application.checkReadyState();
});

$(document).bind("mobileinit", function () {
    Application.mobileInit();
    $(document).one("pageinit", "#Your_First_jQM_Page", function () {
        isjQMReady = true;
        Application.checkReadyState();
    });
});

Application = {
    checkReadyState: function () {
        if (isjQMReady && isPhoneGapReady) {
            Application.ready();
        }
    },
    mobileInit: function () {
        // jQM initialization settings go here
        // i.e. $.mobile.defaultPageTransition = 'slide';
    },
    ready: function () {
        // Both phonegap (if available) and jQM are fired up and ready
        // let the custom scripting begin!
    }
}
6
votes

Interestingly, many answers, but they don't include these three options:

1 – The cordova.js will set the cordova object in the global scope. If it is there then you are most likely running in a Cordova scope.

var isCordovaApp = !!window.cordova;

2 – Cordova will run your application as you would open a HTML document from your Desktop. Instead of the HTTP protocol it will use FILE. Detecting this will give you a chance to assume that your app was loaded locally.

var isCordovaApp = document.URL.indexOf('http://') === -1
  && document.URL.indexOf('https://') === -1;

3 – Use the load event of the cordova script to detect the context. The script include can be easily removed in the build process or the script loading will simply fail in a browser. So that this global variable will not be set.

<script src="../cordova.js" onload="javascript:window.isCordovaApp = true;"></script>

Credit goes to Damien Antipa from Adobe

5
votes

I use this method:

debug = (window.cordova === undefined);

debug will be true on the browser environment, false on the device.

4
votes

This seems to be viable and I have used it in production:

if (document.location.protocol == "file:") {
    // file protocol indicates phonegap
    document.addEventListener("deviceready", function() { $(initInternal);} , false);
}
else {
    // no phonegap, start initialisation immediately
    $(initInternal);
}

Source: http://tqcblog.com/2012/05/09/detecting-phonegap-cordova-on-startup/

3
votes

The essence of the problem is that so long as cordova.device is undefined, your code can't be sure if that's because cordova has established that your device is not supported, or if it's because cordova is still preparing itself and deviceready will fire later (or third option: cordova didn't load properly).

The only solution is to define a waiting period, and to decide that after this period your code must assume the device is not supported. I wish cordova would set a parameter somewhere to say "We've tried finding a supported device and given up" but it seems like there is no such parameter.

Once this is established, you may want to do something specific precisely in those situations where there is no supported device. Like hiding links to the device's app market, in my case.

I've pieced together this function which should cover pretty much every situation. It lets you define a deviceready handler, a device-never-ready handler, and a waiting time.

//Deals with the possibility that the code will run on a non-phoneGap supported
//device such as desktop browsers. Gives several options including waiting a while
//for cordova to load after all.
//In:
//onceReady (function) - performed as soon as deviceready fires
//patience 
//  (int) - time to wait before establishing that cordova will never load
//  (boolean false) - don't wait: assume that deviceready will never fire
//neverReady 
//  (function) - performed once it's established deviceready will never fire
//  (boolean true) - if deviceready will never fire, run onceReady anyhow
//  (boolean false or undefined) - if deviceready will never fire, do nothing
function deviceReadyOrNot(onceReady,patience,neverReady){

    if (!window.cordova){
            console.log('Cordova was not loaded when it should have been')
            if (typeof neverReady == "function"){neverReady();}
        //If phoneGap script loaded...
        } else {
            //And device is ready by now...
            if  (cordova.device){
                callback();
            //...or it's loaded but device is not ready
            } else {
                //...we might run the callback after
                if (typeof patience == "number"){
                    //Run the callback as soon as deviceready fires
                    document.addEventListener('deviceready.patience',function(){
                        if (typeof onceReady == "function"){onceReady();}
                    })
                    //Set a timeout to disable the listener
                    window.setTimeout(function(){
                        //If patience has run out, unbind the handler
                        $(document).unbind('deviceready.patience');
                        //If desired, manually run the callback right now
                        if (typeof neverReady == 'function'){neverReady();}
                    },patience);
                //...or we might just do nothing
                } else {
                    //Don't bind a deviceready handler: assume it will never happen
                    if (typeof neverReady == 'function'){neverReady();} 
                    else if (neverReady === true){onceReady();} 
                    else {
                       //Do nothing
                    }
                }
            }
    }

}
3
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.

3
votes

Another way, based on SlavikMe's solution:

Just use a query parameter passed to index.html from your PhoneGap source. Ie, in Android, instead of

super.loadUrl("file:///android_asset/www/index.html");

use

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

SlavikMe has a great list on where to do this on other platforms.

Then your index.html can simply do this:

if (window.location.href.match(/phonegap=1/)) {
  alert("phonegap");
}
else {
  alert("not phonegap");
}
2
votes

To keep one codebase, what's of interest is the "platform" the code is running on. For me this "platform" can be three different things:

  • 0: computer-browser
  • 1: mobile-browser
  • 2: phonegap/cordova

The way to check for the platform:

var platform;
try {
 cordova.exec(function (param) {
   platform = 2;
  }, function (err) {}, "Echo", "echo", ["test"]);
} catch (e) {
  platform = 'ontouchstart' in document.documentElement ? 1 : 0;
}

Note:

  • This has to be run only after cordova.js has been loaded (body onload(...), $(document).ready(...))

  • 'ontouchstart' in document.documentElement will be present in laptops and desktop monitors that have a touch-enabled screen so it would report a mobile-browser even though it is a desktop. There are different ways to make a more precise check but I use it because it still takes care of 99% of the cases I need. You can always substitute that line for something more robust.

1
votes

Aarons, try

if (PhoneGap.available){
    do PhoneGap stuff;
}
1
votes

GeorgeW's solution is OK, but even on real device, PhoneGap.available is only true after PhoneGap's things has been loaded, e.g. onDeviceReady in document.addEventListener('deviceready', onDeviceReady, false) has been called.

Before that time, if you want to know, you can do like this:

runningInPcBrowser =
    navigator.userAgent.indexOf('Chrome')  >= 0 ||
    navigator.userAgent.indexOf('Firefox') >= 0

This solution assumes that most developers develop using Chrome or Firefox.

1
votes

I have the same issue.

I am leaning towards adding #cordova=true to the URL loaded by the cordova client and testing for location.hash.indexOf("cordova=true") > -1 in my web page.

1
votes

The following works for me with the most recent PhoneGap / Cordova (2.1.0).

How it works:

  • Very simple in concept
  • I inverted the logic of some of the above timeout solutions.
  • Register for the device_ready event (as recommended by the PhoneGap docs )
    • If the event has still NOT fired after a timeout, fallback to assuming a browser.
    • In contrast, the other solutions above rely on testing some PhoneGap feature or other, and watching their test break.

Advantages:

  • Uses the PhoneGap-recommended device_ready event.
  • The mobile app has no delay. As soon as the device_ready event fires, we proceed.
  • No user-agent sniffing (I like testing my app as a mobile website so browser sniffing wasn't an option for me).
  • No reliance on undocumented (and therefore brittle) PhoneGap features/properties.
  • Keep your cordova.js in your codebase even when using a desktop or mobile browser. Thus, this answers the OP's question.
  • Wytze stated above: 'I wish cordova would set a parameter somewhere to say "We've tried finding a supported device and given up" but it seems like there is no such parameter.' So I provide one here.

Disadvantages:

  • Timeouts are icky. But our mobile-app logic doesn't rely on a delay; rather, it is used as a fallback when we're in web-browser mode.

==

Create a brand new blank PhoneGap project. In the provided sample index.js , replace the "app" variable at the bottom with this:

var app = {
    // denotes whether we are within a mobile device (otherwise we're in a browser)
    iAmPhoneGap: false,
    // how long should we wait for PhoneGap to say the device is ready.
    howPatientAreWe: 10000,
    // id of the 'too_impatient' timeout
    timeoutID: null,
    // id of the 'impatience_remaining' interval reporting.
    impatienceProgressIntervalID: null,

    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // `load`, `deviceready`, `offline`, and `online`.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
        // after 10 seconds, if we still think we're NOT phonegap, give up.
        app.timeoutID = window.setTimeout(function(appReference) {
            if (!app.iAmPhoneGap) // jeepers, this has taken too long.
                // manually trigger (fudge) the receivedEvent() method.   
                appReference.receivedEvent('too_impatient');
        }, howPatientAreWe, this);
        // keep us updated on the console about how much longer to wait.
        app.impatienceProgressIntervalID = window.setInterval(function areWeThereYet() {
                if (typeof areWeThereYet.howLongLeft == "undefined") { 
                    areWeThereYet.howLongLeft = app.howPatientAreWe; // create a static variable
                } 
                areWeThereYet.howLongLeft -= 1000; // not so much longer to wait.

                console.log("areWeThereYet: Will give PhoneGap another " + areWeThereYet.howLongLeft + "ms");
            }, 1000);
    },
    // deviceready Event Handler
    //
    // The scope of `this` is the event. In order to call the `receivedEvent`
    // function, we must explicity call `app.receivedEvent(...);`
    onDeviceReady: function() {
        app.iAmPhoneGap = true; // We have a device.
        app.receivedEvent('deviceready');

        // clear the 'too_impatient' timeout .
        window.clearTimeout(app.timeoutID); 
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        // clear the "areWeThereYet" reporting.
        window.clearInterval(app.impatienceProgressIntervalID);
        console.log('Received Event: ' + id);
        myCustomJS(app.iAmPhoneGap); // run my application.
    }
};

app.initialize();

function myCustomJS(trueIfIAmPhoneGap) {
    // put your custom javascript here.
    alert("I am "+ (trueIfIAmPhoneGap?"PhoneGap":"a Browser"));
}
1
votes

I've stumbled on this problem several months ago when beginning our app, because we wanted the app to be "browser-compatible" also (with the understanding that some functionality would be blocked in that scenario: audio recording, compass, etc.).

The only 100% (and I insist on the 100-hundred-percent condition) solution to PRE-determine the app execution context was this:

  • initialize a JS "flag" variable to true, and change it to false when in an all-web context;

  • therefore you can use a call like "willIBeInPhoneGapSometimesInTheNearFuture()" (that's PRE-PG, of course you still need a POST-PG method of checking if you can call PG APIs, but that one is trivial).

  • Then you say: "but how do you determine the execution context?"; the answer is: "you don`t" (because I don't think you can reliably, unless those brilliant folks at PG would do it in their API code);

  • you write a build script that does it for you: one codebase with two variants.

1
votes

Not really an answer to the question, butwhen I test in a desktop browser, I just set a localstorage value to make the browser load the app dispite deviceready not fireing.

function main() {

    // Initiating the app here.
};

/* Listen for ready events from pheongap */
document.addEventListener("deviceready", main, false);

// When testing outside ipad app, use jquerys ready event instead. 
$(function() {

    if (localStorage["notPhonegap"]) {

        main();
    }
});
1
votes

None of which work, unless you remove the PhoneGap Javascript file from the desktop version of the app, which defeats my goal of having one codebase.

Another option would be to use merges folder, see screenshot below.

You can add platform-specific files / override default ones.

(it should do the trick in some scenarios)

enter image description here


In other words: Rather than detecting the browser, you just don't include certain files for desktop build / attach certain files for iOS only.

1
votes

Detect desktop browser even if emulate device is active

Works in Windows and Mac machines. Need to find a solution for linux View details

var mobileDevice = false;
if(navigator.userAgent.match(/iPhone|iPad|iPod|Android|BlackBerry|IEMobile/))
    mobileDevice = true; 

if(mobileDevice && navigator.platform.match(/Win|Mac/i))
    mobileDevice = false; // This is desktop browser emulator

if(mobileDevice) {
    // include cordova files
}
0
votes

I've actually found a combination of two of the techniques listed here has worked the best, firstly check that cordova / phonegap can be accessed also check if device is available. Like so:

function _initialize() {
    //do stuff
}

if (window.cordova && window.device) {
    document.addEventListener('deviceready', function () {
      _initialize();
    }, false);
} else {
   _initialize();
}
0
votes

Try this approach:

/**
 * Returns true if the application is running on an actual mobile device.
 */
function isOnDevice(){
    return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
}

function isDeviceiOS(){
    return navigator.userAgent.match(/(iPhone)/);
}

/**
 * Method for invoking functions once the DOM and the device are ready. This is
 * a replacement function for the JQuery provided method i.e.
 * $(document).ready(...).
 */
function invokeOnReady(callback){
    $(document).ready(function(){
        if (isOnDevice()) {
            document.addEventListener("deviceready", callback, false);
        } else {
            invoke(callback);
        }
    });
}
0
votes

I use a combination of what GeorgeW and mkprogramming suggested:

   if (!navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/)) {
      onDeviceReady();
   } else if (Phonegap.available){
      onDeviceReady();
   } else {
      console.log('There was an error loading Phonegap.')
   }
0
votes

I guess in someways they aren't that different are they? Ha Ha... not funny. Who didn't think this wouldn't be a problem? Here's the simplest solution for your considerations. Push different files to your server then you do to PhoneGap. I'd also temporarily go with the http: check suggested above.

var isMobileBrowserAndNotPhoneGap = (document.location.protocol == "http:");

My interest is in pushing the browsers navbar up, so really I can just delete the isolated script's tag and press rebuild [in DW] (they'll be some cleanup for deployment anyway so this can be one of those tasks.) Anyway I feel it's a good option (considering not much else is available) to efficiently just manually comment out things with isMobileBrowserAndNotPhoneGap when pushing to PG). Again for me in my situation I will simple delete the tag for the (isolated code) file that pushes up the navbar when it's a mobile browser (it will be that much faster and smaller). [So ya if you can isolated the code for that optimized but manual solution.]

0
votes

Slightly modified, but works for me perfectly without any issues.

Intent is to load Cordova only when on embedded device, not on a desktop, so I completely avoid cordova on a desktop browser. Testing and development of the UI and MVVM and so is then very comfortable.

Put this code eg. in file cordovaLoader.js

function isEmbedded() {
    return  
    // maybe you can test for better conditions
    //&& /^file:\/{3}[^\/]/i.test(window.location.href) && 
     /ios|iphone|ipod|ipad|android/i.test(navigator.userAgent);
}

if ( isEmbedded() )
{
   var head= document.getElementsByTagName('head')[0];
   var script= document.createElement('script');
   script.type= 'text/javascript';
   script.src= 'cordova-2.7.0.js';
   head.appendChild(script);
}

Then instead of including cordova javascript itself include cordovaLoader.js

<head>
  <script src="js/cordovaLoader.js"></script>
  <script src="js/jquery.js"></script>
  <script src="js/iscroll.js"></script>
  <script src="js/knockout-2.3.0.js"></script>
</head> 

Ease your work! :)

0
votes
if ( "device" in window ) {
    // phonegap
} else {
    // browser
}
0
votes

Just for info the way in PhoneGap 3.x Mobile Application Development Hotshot

var userLocale = "en-US";
function startApp()
{
// do translations, format numbers, etc.
}
function getLocaleAndStartApp()
{
    navigator.globalization.getLocaleName (
        function (locale) {
            userLocale = locale.value;
            startApp();
        },
        function () {
            // error; start app anyway
            startApp();
        });
}
function executeWhenReady ( callback ) {
    var executed = false;
    document.addEventListener ( "deviceready", function () {
        if (!executed) {
            executed = true;
            if (typeof callback === "function") {
                callback();
            }
        }
    }, false);
    setTimeout ( function () {
        if (!executed) {
            executed = true;
            if (typeof callback === "function") {
                callback();
            }
        }
    }, 1000 );
};
executeWhenReady ( function() {
    getLocaleAndStartApp();
} );

and in YASMF framework

https://github.com/photokandyStudios/YASMF-Next/blob/master/lib/yasmf/util/core.js#L152

0
votes

I was trying with the window objects but it didn't worked as I was opening the remote url in the InAppBrowser. Couldn't get it done. So the best and easiest way to achieve it was to append a string to the url which you need to open from the phonegap app. Then check if the document location has string appended to it.

Below is the simple code for it

var ref = window.open('http://yourdomain.org#phonegap', '_blank', 'location=yes');

You will see a string is added to the url "#phonegap".So in the domain url add the following script

if(window.location.indexOf("#phonegap") > -1){
     alert("Url Loaded in the phonegap App");
}