76
votes

I was curious if anyone knew a javascript based method for detecting whether the web experience was being run as a PWA (progressive web app) or it was simply being run as a standard mobile website (with full browser UI).

Is there any difference between a PWA that is "installed" versus one that isn't but still has the service worker and/or app cache registered?

5
That distinction is somewhat murky, since both are fundamentally built using the same technologies. What exactly do you want to detect? - deceze♦
@deceze basically, whether or not it's been installed via as a legit PWA or if it's just running like a standard site. I'm not all that familiar with service workers and caches but I'm guessing that you can register both without something legitimately being installed as a PWA (like on a phone). Maybe the best way to check is whether the browser UI is visible but I'm not sure if that's accessible via JS. - PorcupineRending
Similar to this but for android stackoverflow.com/questions/18653024/… - ikibiki
Is there any way visually to differentiate between PWA vs Mobile Web ? - vikramvi
@vikramvi Depends on the PWA's app manifest. If it's set to run with display-mode: browser, then it is visually the same as your web browser. However many PWAs use standalone, minimal-ui or fullscreen, which are visually different. - Alex Walker

5 Answers

99
votes

If this is for analytical purposes you could set the start URL in the manifest file to include a query string parameter, ex:

"start_url": "./?mode=standalone"

Then in your JavaScript you are able to check for this query string parameter.

Updated (2020-08-19)

Pete LePage wrote a blog on how to setup a custom dimension in Google Analytics using the code below which checks the display mode using window.matchMedia:

let displayMode = 'browser';
  const mqStandAlone = '(display-mode: standalone)';
  if (navigator.standalone || window.matchMedia(mqStandAlone).matches) {
    displayMode = 'standalone';
  }
ga('set', 'dimension1', displayMode);

Read more: https://petelepage.com/blog/2020/08/measure-understand-how-installed-pwa-users-differ-from-browser-tab-users/

Updated (2017-01-20):

Alternatively you could check in JavaScript using:

if (window.matchMedia('(display-mode: standalone)').matches) {
  console.log("This is running as standalone.");
}
41
votes

Edit 11 Oct 2019: Added an extra switch to check if the app is launched via TWA - document.referrer.includes('android-app://')

This works for all - TWA, Chrome & Safari:

const isInStandaloneMode = () =>
      (window.matchMedia('(display-mode: standalone)').matches) || (window.navigator.standalone) || document.referrer.includes('android-app://');

 if (isInStandaloneMode()) {
    console.log("webapp is installed")
}
15
votes
if (window.matchMedia('(display-mode: standalone)').matches) {
  console.log("This is running as standalone.");
}

This answer is correct but it's worth to mention that PWA could run on plenty of display modes:

  • fullscreen
  • standalone
  • minimal-ui
  • browser

If you run your PWA in a 'fullscreen' mode it will return false so additional checks are necessary like:

function isPwa() {
    return ["fullscreen", "standalone", "minimal-ui"].some(
        (displayMode) => window.matchMedia('(display-mode: ' + displayMode + ')').matches
    );
}

Note that window.matchMedia check will return true for the 'browser' display mode even when it's not an installed PWA app.

2
votes

Progressive enhancement is more a concept than an specific function or method that involves several technologies. Now progressive web apps are base on service workers which you can verify if the browser support it.

// Check for browser support of service worker
if ('serviceWorker' in navigator)

Project lighthouse can help you to detect whether an application is progressive enhanced by performing evaluations of several technologies. Take a look on it.

enter image description here

Hope this help, to clarify.

-1
votes

In my PWA, created with Microsoft Visual Studio 2017, following statement works:

var isPWA = navigator.userAgent.match(/MSAppHost/i);