main/PWAInstaller.js

import Snackbar from './classes/Snackbar';
import customEvent from '../utils/customEvent';
import Cookies from '../vendor/jscookie';
import getQueryParam from '../utils/getQueryParam';

/**
 * Adds custom logic and analytics events to track PWA app installation activity
 *
 * @module PWAInstaller
 * @prop {string} promptEvent - Browser event that will prompt the PWA installation workflow.
 * @prop {object} deferredPrompt - Stored reference to the native PWA prompt event object.
 * @prop {int} limitWidth - Max width at which PWA prompt is shown.
 */
const PWAInstaller = {
	promptEvent: 'beforeinstallprompt',
	cookieName: 'PWAShowPrompt',
	onsignalSelector: '#onesignal-slidedown-container',
	deferredPrompt: null,
	limitWidth: 768,
	button: {
		container: '.l-header__actions',
		selector: '.l-header__secondary--pwa',
		modifier: 'l-header__actions--pwa',
		template: `
			<button class="l-header__button l-header__secondary l-header__secondary--pwa c-headerButton">
				<svg class="c-icon c-icon--red c-headerButton__icon l-header__icon u-show-tablet-portrait" focusable="false">
					<use xlink:href="/wp-content/themes/shaw-globalnews/assets/dist/icons/out/symbol/svg/sprite.symbol.svg#plus"></use>
				</svg>
				<span>Add shortcut</span>
			</button>
		`,
	},

	/**
	 * Bind initial PWA prompt event listeners.
	 *
	 * @method init
	 */
	init() {
		// Show a header button prompt on tablet / desktop
		if ( window.matchMedia( `(min-width: ${this.limitWidth}px)` ).matches ) {
			this.boundShowButton = event => this.showButton( event );
			window.addEventListener( this.promptEvent, this.boundShowButton );
			return;
		}

		// Get cookie values
		const promptCookie = Cookies.get( this.cookieName );
		const skipCookie = getQueryParam( 'gnca-skip' );

		// If it's the first time visiting, show prompt on next visit
		if ( ! promptCookie && skipCookie.length < 1 ) {
			Cookies.set( this.cookieName, 'showPrompt', { path: '/', expires: 60 });
			window.addEventListener( this.promptEvent, event => event.preventDefault() );
			return;
		}

		// Otherwise if the user has dismissed the prompt, don't show again for 2 months
		if ( 'showPrompt' !== Cookies.get( this.cookieName ) && skipCookie.length < 1 ) {
			window.addEventListener( this.promptEvent, event => event.preventDefault() );
			return;
		}

		// If onesignal prompt is present we should also skip the PWA prompt, show it next apge view
		const $onesignalPrompt = document.querySelector( this.onesignalSelector );
		if ( $onesignalPrompt && skipCookie.length < 1 ) {
			window.addEventListener( this.promptEvent, event => event.preventDefault() );
			return;
		}

		// show snackbar when PWA install conditions are met
		this.boundShowSnackbar = event => this.showSnackbar( event );
		window.addEventListener( this.promptEvent, this.boundShowSnackbar );

		// track completed app installation
		window.addEventListener( 'appinstalled', () => {
			this.track( 'install' );
		});
	},

	/**
	 * Fires once the PWA prompt can be shown (typically on `beforeinstallprompt`).
	 * We'll hide the default prompt and show our own custom snackbar prompt.
	 *
	 * @method showSnackbar
	 * @param {object} event - typically the `beforeinstallprompt` event object.
	 */
	showSnackbar( event ) {
		// stash prompt event
		event.preventDefault();
		this.deferredPrompt = event;

		// trigger custom install button
		const snackbar = new Snackbar({
			text: {
				prompt: 'Get your news faster. Add Global News to your Home Screen.',
				dismiss: 'Not Now',
				accept: 'Add to Home Screen',
			},
		});
		snackbar.init();

		// track show snackbar CTA
		this.track( 'display' );

		// show prompt if snackbar is accepted
		snackbar.$element.addEventListener( customEvent.SNACKBAR_ACCEPTED, () => {
			this.showPrompt();
		});

		// otherwise this will fire again as the user interacts with the prompt
		window.removeEventListener( this.promptEvent, this.boundShowSnackbar );

		// don't show snackbar prompt again for 60 days
		Cookies.set( this.cookieName, 'alreadyPrompted', { path: '/', expires: 60 });
	},

	/**
	 * Add a button to the site header to trigger the PWA install prompt on desktop
	 *
	 * @method showButton()
	 * @param {object} event - typically the `beforeinstallprompt` event object.
	 */
	showButton( event ) {
		// stash prompt event
		event.preventDefault();
		this.deferredPrompt = event;

		// bail if button is already added
		let $buttonElement = document.querySelector( this.button.selector );
		if ( $buttonElement ) {
			return;
		}

		// grab container
		const $buttonContainer = document.querySelector( this.button.container );
		if ( $buttonContainer ) {
			$buttonContainer.classList.add( this.button.modifier );
			$buttonContainer.innerHTML = this.button.template + $buttonContainer.innerHTML;
			$buttonElement = document.querySelector( this.button.selector );

			if ( $buttonElement ) {
				$buttonElement.addEventListener( 'click', () => {
					this.track( 'add shortcut' );
					this.showPrompt();
				});
			}
		}
	},

	/**
	 * If the snackbar prompt is accepted, trigger the native PWA install prompt.
	 *
	 * @method showPrompt()
	 */
	showPrompt() {
		if ( this.deferredPrompt ) {
			this.deferredPrompt.prompt();

			if ( this.deferredPrompt.userChoice ) {
				this.deferredPrompt.userChoice.then( ( result ) => {
					if ( 'accepted' === result.outcome ) {
						this.track( 'accepted' );
					} else {
						this.track( 'cancelled' );
					}
				});
			}
		}

		this.deferredPrompt = null;
	},

	/**
	 * Analytics call for tracking PWA app installation activity
	 *
	 * @method track
	 * @param {string} eventName - event name to track.
	 */
	track( eventName ) {
		/* global gn_analytics */
		/* eslint-disable camelcase */
		if ( 'undefined' !== typeof ( gn_analytics ) ) {
			const data = {};
			data['pwa.homebanner'] = eventName;
			gn_analytics.Analytics.track(['adobe', 'ga'], {
				action: `PWA | ${eventName}`,
				data,
			});
		}
		/* eslint-enable camelcase */
	},
};

export default PWAInstaller;