检查用户是否已在 Chrome 上将 PWA 安装到主屏幕?

check if user has already installed PWA to homescreen on Chrome?

我正在尝试在我的渐进式网络应用程序上创建一个 "Add To Home Screen" 按钮,如 Chrome's documentation 中所述。

我通常遵循规定的模式,我有一些隐藏按钮,当 Chrome 的 beforeinstallprompt 事件触发时显示。 一旦事件触发,我就捕获该事件,然后在单击我自己的安装按钮后使用该事件开始本机安装对话。示例代码如下:

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent Chrome 67 and earlier from automatically showing the prompt
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
  // Update UI notify the user they can add to home screen
  btnAdd.style.display = 'block';
});

btnAdd.addEventListener('click', (e) => {
  // hide our user interface that shows our A2HS button
  btnAdd.style.display = 'none';
  // Show the prompt
  deferredPrompt.prompt();
  // Wait for the user to respond to the prompt
  deferredPrompt.userChoice
    .then((choiceResult) => {
      if (choiceResult.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt');
      } else {
        console.log('User dismissed the A2HS prompt');
      }
      deferredPrompt = null;
    });
});

我 运行 遇到的问题是,如果用户已经在他们的主屏幕上安装了网络应用程序,我不想显示我的安装按钮 (btnAdd),并且我无法弄清楚如何检查这种情况。

本来希望把上面的代码修改成如下:

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent Chrome 67 and earlier from automatically showing the prompt
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;

  // If the user has not already installed...
  deferredPrompt.userChoice
    .then(choiceResult => {
      if (choiceResult === undefined) {
        // Update UI notify the user they can add to home screen
        btnAdd.style.display = 'block';
      }
    });
});

如果用户已经安装,则不会显示安装按钮。但这似乎不起作用。看来,如果他们还没有做出选择,访问 userChoice 只会直接用本机对话提示用户。

我不太确定 beforeinstallevent 是如何工作的,所以这甚至可能不是一个好的策略。理想情况下,我希望这会像 navigator.serviceWorker.ready() 那样工作,其中 returns 是一个 Promise,而不是使用浏览器事件来尝试弄清楚什么时候准备好了。

无论如何,在显示我自己的主屏幕安装按钮之前,是否有关于如何检查用户是否已安装到主屏幕的想法?

编辑: 正如 Mathias 评论的那样,在显示按钮之前检查事件就足够了。我相信我遇到的问题是使用 localhost 的结果,它似乎在安装后不断触发 beforeinstallprompt 事件,这不是预期的行为。托管代码解决了这个问题。

也许,在拦截自动弹出窗口之前不要显示按钮?


在您的代码中,检查 window 是否独立
如果是,则无需显示按钮

if (window.matchMedia('(display-mode: standalone)').matches) {  
    // do things here  
    // set a variable to be used when calling something  
    // e.g. call Google Analytics to track standalone use   
}  

这里是我的示例测试人员
https://a2hs.glitch.me

我的测试器的源代码
https://github.com/ng-chicago/AddToHomeScreen

HTML

<!-- start with hidden button -->
<button id="install" style="display:none;">install</button>

JAVASCRIPT

// variable store event
window.deferredPrompt = {};

// get button with id
const install_button = document.querySelector('#install');

// if the app can be installed emit beforeinstallprompt
window.addEventListener('beforeinstallprompt', e => {
  // this event does not fire if the application is already installed
  // then your button still hidden ;)

  // show button with display:block;
  install_button.style.display = 'block';

  // prevent default event
  e.preventDefault();

  // store install avaliable event
  window.deferredPrompt = e;

  // wait for click install button by user
  install_button.addEventListener('click', e => {
    window.deferredPrompt.prompt();
    window.deferredPrompt.userChoice.then(choiceResult => {
      if (choiceResult.outcome === 'accepted') {
        // user accept the prompt

        // lets hidden button
        install_button.style.display = 'none';
      } else {
        console.log('User dismissed the prompt');
      }
      window.deferredPrompt = null;
    });
  });
});

// if are standalone android OR safari
if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true) {
  // hidden the button
  install_button.style.display = 'none';
}

// do action when finished install
window.addEventListener('appinstalled', e => {
  console.log("success app install!");
});

我不明白这怎么是正确答案,因为这基本上是检查用户是否已经使用该应用程序,但我们不想要的行为是 "When the user is on the web and tries to install the app again to tell him that he already has the app in his device"。在我看来,这不是解决这个问题的答案。

我们能做的是: 1. 当用户点击安装但他的设备上有应用程序时 在这种情况下,beforeinstallprompt 事件 不会被 触发,因此该事件将 return 为空。我们将结果存储在全局变量中,当结果为空时,我们向用户显示他已经安装了该应用程序。 2. 当用户点击安装但他的设备上没有应用程序时 在这种情况下,beforeinstallprompt 事件 将被触发 因此该事件将 return 访问以显示提示。 我们可以将结果存储在全局变量中,如果它不为 NULL(不会为 NULL),因为如果用户的设备上没有应用程序, beforeinstallprompt 将被触发,我们将 prompt() 显示给用户。

我怀疑我的解决方案是否也很好,但我认为问题和正确答案没有任何共同点

window.addEventListener("beforeinstallprompt", event => {
  window.deferedPrompt = event;
});

handleButtonClick = () => {
 const promptEvent = window.deferedPrompt;
 if(!promptEvent){
 // DO SOMETHING
 }
//Show the add to home screen prompt
promptEvent.prompt()

promptEvent.userChoice.then((result: any) => {
      // Reset the deferred prompt variable, since
      // prompt() can only be called once.
      window.deferedPrompt = null;.
    });
}



<button onClick={handleButtonClick}>Install</button>

回答原问题。对于 Chrome 的最新版本,您可以使用 window.navigator.getInstalledRelatedApps()。它 returns 一个承诺,其中包含一系列已安装的应用程序,您的网络应用程序在 manifest.json 中将其指定为相关。要启用此功能,您需要将 related_applications 字段添加到 manifest.json

  "related_applications": [{
    "platform": "webapp",
    "url": "https://app.example.com/manifest.json"
  }]

然后你可以像这样使用它:

//check if browser version supports the api
if ('getInstalledRelatedApps' in window.navigator) {
  const relatedApps = await navigator.getInstalledRelatedApps();
  relatedApps.forEach((app) => {
    //if your PWA exists in the array it is installed
    console.log(app.platform, app.url);
  });
}

来源:API docs

现在您可以显示一些元素,具体取决于您的应用程序是否已安装。例如:您可以显示“打开应用程序”按钮并将用户重定向到 PWA。但是请记住,当用户已经在使用@Mathias 的回答并检查 (display-mode: standalone)

的应用程序中时禁用它

但是,关于您的用例。只有当 beforeinstallprompt 被拦截时,您才应该显示安装按钮。如果 PWA 已安装在设备上,浏览器不会触发此事件。当提示被触发并且 choiceResult.outcome === 'accepted' 你再次隐藏按钮。

这是告诉您的简单功能,此应用程序在浏览器或 pwa 中打开。 原始来源 link

function getPWADisplayMode() {
  const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
  if (document.referrer.startsWith('android-app://')) {
    return 'twa';
  } else if (navigator.standalone || isStandalone) {
    return 'standalone';
  }
  return 'browser';
}