Create a PWA App Manifest Dynamically | SPA | Angular

Vladislav Stanković
limehome-engineering
7 min readApr 4, 2022

At Limehome, I work as a Front-End developer in the Customer Journey team. We recently converted Guest Hub to PWA to make our customer’s journey smoother and more convenient. Guest Hub is a place where our customers can access their reservation and other information relevant to their stay at the Limehome suite.

This article describes different approaches we considered in order to pass authentication data from browser to PWA instance.

One tap Guest Hub authentication with PWA

The Goal:

Allow guests to access Guest Hub with one tap of the icon on their phone.

We wanted to eliminate the need for our guests to type in their credentials in order to access their reservation information. All they have to do is get authenticated once and install the PWA to their phone’s home screen. Then they can simply tap the icon to access the Guest Hub anytime— pretty convenient, right?

Technical challenge:

To achieve the goal we had to overcome technical challenges of passing credentials data (user and reservation information) from browser to PWA instance. We had to:

  • Invoke the browser beforeinstallprompt event to install PWA icon to phone’s home screen.
  • Pass data from browser to PWA so we can authenticate the user and let them access the Guest Hub page.
  • Handle PWA redirection from Homepage to Guest Hub within the Angular SPA.
  • Make the solution compatible with Android and iOS devices.

How it should work:

  • After accessing the Guest Hub page a modal opens promoting the PWA installation.
  • On Android: User clicks “Add to home screen” button from the modal. A prompt appears prompting the user to add PWA to home screen. After confirming the prompt an icon is installed to phone’s home screen.
  • On iOS: User taps the “Share” icon (at the bottom bar in Safari) and selects “Add to home screen“ from the browser menu. After confirming, the icon should appear on the phone’s home screen.
  • Tapping the icon starts the PWA and user is authenticated and redirected to Guest Hub where they can access their reservation info.
Safari does not support beforeinstallprompt so we added instructions for iOS users

Approaches:

(#3 was viable and accepted, for details check the Implementation section below)

#1 — Create API endpoint to generate the manifest file:
The idea was that after the user is authenticated we can set the web manifest link href value inside the index.html to point to API endpoint where we generate the manifest dynamically.

Inside the generated manifest we set the start_url value to contain query parameters containing reservation data, something like:

/guest-hub?lastname=Somename&id=XXXXXXX-1

This approach was NOT viable. Both location and origin of the manifest file were different from index.html file and hence the browser was unable to uniquely identify the PWA. This was preventing the PWA from registering correctly and the install event (beforeinstallprompt) would not be fired.

#2 — Share data with PWA using Cookies / Cache storage:
The idea was to save credentials data inside the Cookies or Cache storage after the guest is authenticated. After the PWA is installed and started we can check if credentials are stored inside Cookies or Cache Storage. Then we can simply redirect the user to the Guest Hub page and provide this data as part of the URL parameters for authentication.

This approach was NOT viable.
Although this approach worked for Android it was not viable for iOS devices.

On Android, both Cookies and Cache Storage are shared between the browser and PWA instance. However, Safari does NOT share Cookies or Cache Storage with the PWA instance and hence this wouldn’t work on iOS devices.

All iOS browsers are basically a Safari with different interface and hence inherently preventing any type of data sharing between PWA and the browser.

#3 — Accepted solution : Generate the manifest file on client side:
The idea was to create manifest inside the browser. We create contents of the manifest file as JSON object where we can set start_url dynamically to contain the data that we need by providing query parameters. Then we create a Blob and append to web manifest link href value inside the index.html.

When PWA is started we can get the user and reservation data from start_url query parameters, redirect to the Guest Hub page and authenticate the user. The best part is that it works on both Android and iOS devices.

This approach was viable and implemented.
It allowed us to avoid data sharing constraints for iOS browsers. We could simply use manifest to pass data to PWA by appending query parameters to start_url value.

Technical requirements, restrictions and gotcha’s:

In order for PWA to be recognized and installed by the browser certain criteria must be met:

  • The app has to be served via https.
  • Valid Service Worker has to be registered.
  • Valid web manifest file has to be present with valid start_url and scope values set.

Things to keep in mind:

  • In order to show the browser prompt to install PWA to home screen we have to use beforeinstallprompt event. However, it will NOT be fired in Safari.
  • beforeinstallprompt event will also NOT be fired when private browsing is used.
  • Swapping the manifest link href value after service worker is registered will break the PWA functionality. If manifest href attribute was present (inside index.html) when the app is loaded, the identity of the PWA is tied to that initial manifest. So if we touch the manifest link href (by setting a different href value) the browser will no longer identify Service Worker and Manifest as part of the same PWA, it will break the functionality. But there is a workaround!
  • The workaround: Our final approach goes around this behavior by leaving out the href attribute completely from the index.html. We are not even using empty href value, but instead manifest link element is completely missing the href attribute inside the index.html:
    <link rel="manifest" id="web-manifest" />
    This will prevent registering the PWA until we generate the dynamic manifest. Then, after the dynamic manifest has been generated and blob has been created, we append the href attribute and PWA registers successfully.

Implementing the solution:

We saved the content of our manifest as one of the constants inside the project so we can manipulate it later when generating dynamic values. Inside a dedicated service we are dynamically updating the start_url by appending query parameters.

Lets say parameters that we are passing are lastName and id :

import { webmanifest } from '../constants.ts'const startUrl = "https://www.ourwebsite.com"
const pwaAuthParams = `?lastName=${name}&id=${id}`;
handleDynamicManifest(startUrl, pwaAuthParams) {
// getting web manifest from constants and modifying values
let myDynamicManifest = webManifest;

myDynamicManifest.start_url = `${startUrl}${pwaAuthParams}`;
myDynamicManifest.scope = startUrl;

// appending modified manifest href value to index.html
const stringManifest = JSON.stringify(myDynamicManifest);
const blob = new Blob([stringManifest], { type: 'application/json' });
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#web-manifest').setAttribute('href', manifestURL);
}

As mentioned earlier, href attribute for the manifest link element is missing inside index.html file up until this point. After the user is authenticated we set dynamic values in the manifest. Then we are creating a Blob and setting the href attribute for the manifest element.

At this point browser has a valid Service Worker and valid manifest and triggers the beforeinstallprompt event (in non iOS browsers).

beforeinstallprompt in Chrome on Android device

When this event is triggered, browser will show the prompt asking user to actually add the PWA shortcut to home screen.

We wanted to have a nice “Add to home screen“ button for Android users prompting them to install the PWA so had to save this event for later as we didn’t want to trigger it right away. To prevent this prompt from showing right away we are saving it for later via service.

@HostListener('window:beforeinstallprompt', ['$event'])
onbeforeinstallprompt(e) {
e.preventDefault();
// Save the event so it can be triggered later.
this.myPwaService.savePwaPromptEventForLater(e);
}

Safari turns out to be the party-breaker once again as beforeinstallprompt event can NOT be fired from Javascript in Safari. It does not support promoting installation of PWA unlike Chrome-based browsers. This means we can only show “Add to home screen” button for non-iOS browsers.

For iOS browsers we are showing a textual content instructing users to tap the icon in their browser and find “Add to home screen“ option inside the browser menu.

Handling the PWA redirection

When our PWA is started we are handling the URL query parameters, redirecting and authenticating the user.

First we check if the app is started as PWA (standalone) and if required parameters are present. Then we are redirecting the user to Guest Hub.

// Check if the app is started in standalone mode
isPwaStandalone() {
return window.navigator['standalone'] || window.matchMedia('(display-mode: standalone)').matches;
}
// When app is loaded check query params and handle redirectionhandlePwa() {
const lastnameParam = this.route.snapshot.queryParamMap.get('lastName');
const idParam = this.route.snapshot.queryParamMap.get('id');

if (this.isStandalone() && lastnameParam && idParam) {
// redirecting
}
}

Conclusion

Passing data from browser to PWA instance can be tricky, especially since there is no single solution supported by all major browsers. A good indicator of how well PWA features are supported across the browser market can be seen by checking the browser support for our beloved beforeinstallprompt.

Chrome and Chromium based browsers are definitely making working with Progressive Web Apps easier as they allow for shared Local Storage and Cache storage between the browser and PWA. On the other hand, Safari supports some features while lagging behind with others.

By the way, check out the open roles in our Tech and Engineering team.

--

--