Some months ago, I had written a word game as a Flask web application. The code was inside my laptop, and I decided to put it out in the public domain. However, to give it a mobile app feel, I decided to make it a Progressive Web Application (PWA).
The first requirement for a PWA is that it should be served over HTTPS. My cloud server runs on Ubuntu which has snap pre-loaded, so the easiest way to switch to HTTPS is to run snap to install the utility certbot and run certbot for installing Lets Encrypt certificates and configuring Nginx. But Lets Encrypt certificates can be installed for domain names only, not for IP addresses. I did a quick search for domain names, and the cheapest one available was mahboob.xyz, which was perfect for hosting my side projects, so I bought it.
The commands I ran as root for enabling HTTPS are as follows:
For the IP address to domain name mapping, all I had to do was go into my domain registrar account and give the named servers of my hosting provider. In the hosting account control panel, I went to the Domains section and in just very few clicks the mapping was done.
As John Price explained in his article, How to Turn Your Website into a PWA, the advantages of a PWA are:
- Offline capable
- Improved performance
- App-like experience
- Push notifications
- Always up to date
The real kicker is the last point. You don’t have to invest time and money to write any mobile application code, whether native or cross-platform. Whatever you have used for your responsive web application is good to go; with minimal changes, it gets the features and feel of a mobile application.
My application is a simple word game. The user gets a clue and they have to guess the word. On a button click, they will be told whether they got the answer right or not. If the user wants to know the answer they will be shown the answer. If the user wants a different word, they will get a new clue for it.
The clues and answers are stored in a pipe-delimited flat file on the server. A couple of lines from the file are shown in the following screenshot.
When the Flask application starts, it reads the file contents into a data array. The root action is an index, which reads a random element from the array, splits it on the pipe character, and sends the first part (the clue) and the element’s index to the game page. The clue is displayed as text and the index is kept as a hidden variable.
- Check: It invokes the action method check, sending the index and the answer the user entered in the text field. The action retrieves the element from the data array using the index, splits it into pipe character and checks the second part (correct answer) with the user’s answer. If they are the same, the method returns “You got it right!”, else it returns “Wrong Answer! Please try Again!!”. If the answer is correct, this button itself and the “Show Answer” button are hidden.
- Show Answer: This button invokes the action show, sending the index. The action method retrieves the element from the data array using the index, splits it into pipe character, and sends the correct answer back to the game page. After receiving the response from the server, the game page hides the input text field, Check and Show Answer buttons.
- New Word Clue: The functionality for this button is the same as the index. It invokes the action “new”, which reads a random element from the array, splits it on the pipe character, and sends the first part (the clue) and the element’s index to the page. The answer text field is cleared and the Check and Show Answer buttons are explicitly unhidden by calling the show method.
All the buttons make AJAX GET calls via JQuery.
In order to convert a web application to a PWA, there are three main requirements.
- Run it over HTTPS.
- Create and serve a manifest file in JSON format.
serviceworker.js. The game page registers it with the following code:
I used the online Web App Manifest Generator to create
manifest.json. The manifest.json and serviceworker.js files are placed in the static folder, from which Flask serves public assets without requiring server-side routing.
The serviceworker.js file has event listeners for installing itself, opening the cache, activating the cache, and adding/fetching URLs and responses to/from the cache. It also handles two custom features in the fetch event handler.
- Getting a new word clue should not be cached. If it’s cached, the same clue will be shown again and again from the cache. Preventing this is achieved by a check for the URL “https://mahboob.xyz/wtgw/new” and if yes, the code returns from the function, thus giving a pass-through to the server without checking the cache.
- If the user event has not been cached and the user is not connected to the internet, then the call to the cache returns with the response “You seem to be offline, please try after you’re online.”
The fetch event handler code is shown below: /static/serviceworker.js
On mahboob.xyz, the application is run by gunicorn, which requires the file wsgi.py, and is set up as a service. It is co-located along with two Rails applications. The application service and Nginx configuration are given below: /etc/systemd/system/wtgw.service