UNPKG

@khmyznikov/pwa-install

Version:

PWA install dialog provide more convenience user experience and fix lack of native dialogs in some browsers.

145 lines (129 loc) 5.69 kB
import { WebAppManifest } from 'web-app-manifest'; import { IRelatedApp, Manifest } from './types/types'; const _eventDispatcher = (_element: Element, name: string, message: string) => { const event = new CustomEvent(name, { detail: { message } }); _element.dispatchEvent(event); } export default class Utils { static isAppleMobile(): boolean { if ( ( ['iPhone', 'iPad', 'iPod'].includes(navigator.platform) || (navigator.userAgent.match(/Mac/) && navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ) && ('serviceWorker' in navigator) ) return true; return false; } static isAppleDesktop(): boolean { // check if it's a mac const userAgent = navigator.userAgent.toLowerCase(); if (navigator.maxTouchPoints || !userAgent.match(/macintosh/)) return false; // check safari version >= 17 const version = /version\/(\d{2})\./.exec(userAgent) if (!version || !version[1] || !(parseInt(version[1]) >= 17)) return false; // hacky way to detect Sonoma const audioCheck = document.createElement('audio').canPlayType('audio/wav; codecs="1"') ? true : false; const webGLCheck = new OffscreenCanvas(1, 1).getContext('webgl') ? true : false; return audioCheck && webGLCheck; } static isAndroid(): boolean { if (navigator.userAgent.toLowerCase().match(/android/)) return true; return false; } static isAndroidFallback(): boolean { if (this.isAndroid() && !('BeforeInstallPromptEvent' in window)) return true; return false; } static deviceFormFactor(): 'narrow' | 'wide' { return window.matchMedia('(orientation: portrait)').matches? 'narrow' : 'wide'; } static isStandalone() { if (window.matchMedia('(display-mode: standalone)').matches || ('standalone' in navigator && (navigator as any).standalone === true)) return true; return false; } static async getInstalledRelatedApps(): Promise<IRelatedApp[]> { if ('getInstalledRelatedApps' in navigator) try{ return await (navigator as any).getInstalledRelatedApps().then((relatedApps: IRelatedApp[]) => { return relatedApps; }); } catch (e) {} return []; } static async isRelatedAppsInstalled(): Promise<boolean> { const _relatedApps = await this.getInstalledRelatedApps(); return _relatedApps.length? true : false; } static setStorageFlag(name: string, value:boolean, persistent: boolean = false) { try { if (persistent) localStorage.setItem(name, value.toString()); else sessionStorage.setItem(name, value.toString()); } catch (e) {} } static getStorageFlag(name: string): boolean { try { return sessionStorage.getItem(name) === 'true' || localStorage.getItem(name) === 'true'; } catch (e) { return false; } } static eventInstalledSuccess(_element: Element) { _eventDispatcher(_element, 'pwa-install-success-event', 'App install success (Chromium/Android only)'); } static eventInstalledFail(_element: Element) { _eventDispatcher(_element, 'pwa-install-fail-event', 'App install failed (Chromium/Android only)'); } static eventUserChoiceResult(_element: Element, message: string) { _eventDispatcher(_element, 'pwa-user-choice-result-event', message); } static eventInstallAvailable(_element: Element) { _eventDispatcher(_element, 'pwa-install-available-event', 'App install available'); } static eventInstallHowTo(_element: Element) { _eventDispatcher(_element, 'pwa-install-how-to-event', 'App install instruction showed'); } static eventGallery(_element: Element) { _eventDispatcher(_element, 'pwa-install-gallery-event', 'App install gallery showed'); } static normalizeManifestAssetUrls(manifest: WebAppManifest, manifestUrl: string) { const normalizedManifestUrl = new URL(manifestUrl, document.location.href); [...manifest.icons || [], ...manifest.screenshots || []].forEach(asset => { asset.src = new URL(asset.src, normalizedManifestUrl).href; }) } static async fetchAndProcessManifest(manifestUrl: string, icon: string, name: string, description: string): Promise<{_manifest: WebAppManifest, icon: string, name: string, description: string}> { let _manifest: WebAppManifest = new Manifest(); let _json: WebAppManifest | null = null; try{ const _response = await fetch(manifestUrl); _json = await _response.json() as WebAppManifest; if (!_response.ok || !_json || !Object.keys(_json)) throw new Error('Manifest not found'); this.normalizeManifestAssetUrls(_json, manifestUrl); } catch(e) {} icon = icon || (_json?.icons?.length ? _json?.icons![0].src : _manifest.icons?.[0].src) || ''; name = name || (_json? _json['short_name']: _manifest['short_name']) || ''; description = description || _json?.description || _manifest.description || ''; _manifest = _json || _manifest; return { _manifest, icon, name, description } } }