In this post we turn our great web app into a Progressive Web App (PWA). "A progressive web app provides an app-like user experience that is low friction and is built using modern web capabilities and hosted on the web and can become an app on the user's system over time." To learn more about the great promises of Progressive Web Apps read Addy Osmani's great overview article Getting started with Progressive Web Apps. Google also has a great entry point to the required information at https://developers.google.com/web/progressive-web-apps/.
Getting up to speed with Progressive Web Apps
Progressive Web Apps are cool, and in my opinion the future of a whole category of mobile apps. An inspiring introduction to this vision is this presentation by Bruce Lawson. See also https://dev.opera.com/blog/pwa-taipei/. For a great overview of other material available on Progressive Web Apps have a look at https://github.com/hemanth/awesome-pwa.
What do I want to achieve in this blog post
In this article we explore how to get the following functionality working in our PWA:
- Hosting in a SharePoint document library - for authentication, access to data within your company, and freedom of distribution
- Add to homescreen - so we get an icon between the other apps on your mobile device or between the apps in your browser
- Splash screen - for direct visual feedback on start of our application
- Offline support - so we can use our app even if we are not connected to the network
The code for our sample Progressive Web App can be found at https://github.com/svdoever/sharepoint-progressive-web-apps/tree/master/ShowTitleProgressiveWebApp.
For the PWA we need things like a favicon, icons for the different (mobile) platforms, html code to include these icons, and configuration directives for platforms to provide us with PWA functionality. The site http://realfavicongenerator.net/ can help us out with some of these steps. It generates a zip file with icons and other artifacts based on a single uploaded icon. Because we are creating the amazing "Show Title" app that shows the title of SharePoint site hosting our app, I downloaded an icon of the letter T and used that for the generation. The generator created a zip file with artifacts and the following HTML lines for inclusion in the head of our app page:
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/manifest.json">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="theme-color" content="#ffffff">
Because we are hosting in a subfolder of a SharePoint document library and not in the root of a website we need to remove the leading slashes as in:
<link rel="apple-touch-icon" sizes="60x60" href="apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="76x76" href="apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="120x120" href="apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="manifest.json">
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5">
<meta name="theme-color" content="#ffffff">
Besides a set of icons it generated a browserconfig.xml file for Internet Explorer with the following contents (leading slashes removed):
<?xml version="1.0" encoding="utf-8"?>
The web manifest file
Now we need to add a manifest file that drives our Progressive Web App. The specifications for the app manifest can be found at https://www.w3.org/TR/appmanifest/, but a better readable reference can be found at https://developer.mozilla.org/en-US/docs/Web/Manifest. In most examples, and in the generated zip file by the generator, the manifest file is named manifest.json. When hosting in SharePoint files ending in .json are not allowed. We will rename the file to manifest.webmanifest.
I made some small changes to the generated manifest, like adding a short_name and description and background_color (used as background color for the splash screen), and I added 128x128 and 144x144 version of the icon (I resized the 256x256 icon using a paint program) as that seems to be required by Firefox (128x128) and for the splash screen support (144x144). The start_url we set to index.html?home=true so that when we add the app to homescreen we can identify that it is an app lounched from the homescreen.
The manifest file manifest.webmanifest:
End of file manifest.webmanifest.
If we look into the descriptions about add to homescreen requirements (https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/) we find that the site:
- Has a web app manifest file with:
short_name(used on the home screen)
name(used in the banner when showing the splash screen)
- a 144x144 png icon used on the splash screen, (the icon declarations must include a mime type of
- Has a service worker registered on your site (service worker may be empty)
- Is served over HTTPS (a requirement for using service worker).
- Is visited at least twice, with at least five minutes between visits.
The app page index.html
In the index.html file we see the registration of the service worker. Besides the service worker registration there is one really important thing: the link to the web manifest. When specifying the link to the manifest file as found in all examples as
<link rel="manifest" href="manifest.webmanifest"> it works perfectly locally, but does not work when deployed to SharePoint because authentication headers are not passed through on requesting the manifest. I tried everything. Providing the manifest content from a page manifest.aspx so I could set the correct content type (made no difference). Embedding the manifest as base64 in the href on the link (seems not supported on Chrome, see https://github.com/w3c/manifest/issues/534). The answer came on a question I posted on https://github.com/w3c/manifest/issues/535: we need to use the attribute crossOrigin="use-credentials", now the authentication headers are passed as required. So the manifest should be referenced as:
<link rel="manifest" href="manifest.webmanifest" crossOrigin="use-credentials">. I think this is a bug, because on access of resources on the same origin the security context should be inherited (https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
The file index.html:
End of file index.html.
Testing the app from a browser and from a device
During development we use the sp-rest-proxy and can access the site on http://localhost:8081. We can now test the app from for example Chrome. Testing the app in Chrome is interesting because Chrome has support for web app manifests in the developer tools on the application tab. From this tab it is possible to see if the manifest is correctly parsed and we can test the Add to homescreen functionality:
We now have an outside https endpoint to access our app. Now open https://471e3571.ngrok.io/index.html to open the app from another device like your mobile.
Deploying to SharePoint
Building on the SharePoint configuration described in the previous blog posts we can now deploy our app to SharePoint. The custom deployment script deploy.js will work for our current situation.
The file deploy.js:
End of file deploy.js.
On deployment we rename the index.html page to index.aspx. But we also need to do rewrites on the index.html and manifest.webmanifest files.
Because we need to deploy more artifacta to SharePoint, the deploy.js command got an optional parameter. If you give no parameter only the index.html and manifest.webmanifest files are deployed. If the parameter assets is given, also all other artifacts are deployed.
For deployment we no support the following npm commands:
- npm start codedeploy - only deploy the index.html and manifest.webmanifest files
- npm start deploy - deploy all files (makes the call node deploy.js assets)
When no offline support is required we can get away with an empty service-worker.js file. When we want offline support we need to implement caching functionality like the first approach below. In this first approach I don't remove old caches, and when cached I don't load newer versions. Not even of /_api/ calls. When creating a new version of the app we can increase the version number of the cache to cache the latest versions again. For api call we need another strategy. For more information on caching strategies see Jake Archibalds blog post https://jakearchibald.com/2016/caching-best-practices/. There is also agreat service workers cookbook at https://serviceworke.rs/ describing different caching strategies. In an upcoming blog post I will describe what I think would be the optimal caching strategy for SharePoint hosted apps and /_api service calls.
The file service-worker.js:
End of file service-worker.js.
Easy access to your SharePoint hosted PWA
The links into SharePoint for your Progressive Web App can become quite large, and not easy to share with others. For easy access on devices I registered a short link https://bit.ly/sptitle to my app using the https://bit.ly shortening service.