NGO-DB/ndb-core

View on GitHub
src/app/core/pwa-install/pwa-install.service.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { Inject, Injectable } from "@angular/core";
import { WINDOW_TOKEN } from "../../utils/di-tokens";

export enum PWAInstallType {
  ShowiOSInstallInstructions,
  RunningAsPWA,
  NotAvailable,
}

enum Browser {
  Opera,
  MicrosoftInternetExplorer,
  Edge,
  Safari,
  Chrome,
  Firefox,
  Other,
}

enum OS {
  iOS,
  MacOS,
  Android,
  Linux,
  Windows,
  Other,
}

@Injectable({ providedIn: "root" })
export class PwaInstallService {
  /**
   * Resolves once/if it is possible to directly install the app
   */
  static canInstallDirectly: Promise<void>;

  private static deferredInstallPrompt: any;

  constructor(@Inject(WINDOW_TOKEN) private window: Window) {}

  static registerPWAInstallListener() {
    this.canInstallDirectly = new Promise((resolve) => {
      window.addEventListener("beforeinstallprompt", (e) => {
        e.preventDefault();
        this.deferredInstallPrompt = e;
        resolve();
      });
    });
  }

  installPWA(): Promise<any> {
    if (!PwaInstallService.deferredInstallPrompt) {
      throw new Error(
        "InstallPWA called, but PWA install prompt has not fired.",
      );
    }
    PwaInstallService.deferredInstallPrompt.prompt();
    return PwaInstallService.deferredInstallPrompt.userChoice;
  }

  getPWAInstallType(): PWAInstallType {
    const os: OS = this.detectOS();
    const browser: Browser = this.detectBrowser();
    const standaloneMode: boolean = this.detectStandaloneMode();
    let pwaInstallType: PWAInstallType;
    if (standaloneMode) {
      pwaInstallType = PWAInstallType.RunningAsPWA;
    } else if (os === OS.iOS && browser === Browser.Safari) {
      pwaInstallType = PWAInstallType.ShowiOSInstallInstructions;
    } else {
      pwaInstallType = PWAInstallType.NotAvailable;
    }
    return pwaInstallType;
  }

  private detectOS(): OS {
    let os: OS;
    const userAgent = this.window.navigator.userAgent;
    if (/iphone|ipad|ipod|macintosh/i.test(userAgent)) {
      if (this.window.innerWidth < 1025) {
        os = OS.iOS;
      } else {
        os = OS.MacOS;
      }
    } else if (/android/i.test(userAgent)) {
      os = OS.Android;
    } else if (/windows|win32|win64|WinCE/i.test(userAgent)) {
      os = OS.Windows;
    } else if (/linux|X11/i.test(userAgent)) {
      os = OS.Linux;
    }
    return os;
  }

  private detectBrowser(): Browser {
    let browser: Browser;
    const userAgent = this.window.navigator.userAgent;
    if (/opera/i.test(userAgent)) {
      browser = Browser.Opera;
    } else if (/msie|trident/i.test(userAgent)) {
      browser = Browser.MicrosoftInternetExplorer;
    } else if (/edg/i.test(userAgent)) {
      browser = Browser.Edge;
    } else if (/chrome/i.test(userAgent)) {
      browser = Browser.Chrome;
    } else if (/safari/i.test(userAgent)) {
      browser = Browser.Safari;
      if (/crios|fxios/i.test(userAgent)) {
        browser = Browser.Chrome;
      }
    } else if (/firefox/i.test(userAgent)) {
      browser = Browser.Firefox;
    } else {
      browser = Browser.Other;
    }
    return browser;
  }

  private detectStandaloneMode(): boolean {
    return (
      ("standalone" in this.window.navigator &&
        !!this.window.navigator["standalone"]) ||
      this.window.matchMedia("(display-mode: standalone)").matches
    );
  }
}