This project demonstrates how to develop a Progressive Web Application (PWA) locally on an Android device, using the Quasar Framework v2.
The main goal is to run an application in hot-reload mode, and make it available to a physical Android device. I'm using Android (Samsung Galaxy S10, Android 11), but I'm sure there's a way to tweak this project to make it work on iOS.
- Challenges
- Requirements
- PWA with hot-reload via HTTP
- Setup A Local DNS Server
- Access PWA From Local Machine
- Access PWA From An Android Device
- Set An HTTPS Connection From Local Machine To PWA
- Set An HTTPS Connection From An Android Device To PWA
- Controlling The Android Device With Google Chrome
- Conclusions
- Local Development
- Troubleshooting
- Useful Resources
- Authors
- License
This is how it all happened - documenting my learning process for future me
- I wanted to know if a PWA can send Push Notifications, like a normal application. For example, if the device is locked up and quiet, will it ring and buzz? Will I get notification from the PWA?
- To make it work, I wanted to have a local development environment in hot-reload mode, so I can test the PWA on an Android device as the code changes. Previously mentioned, I'm on Samsung Galaxy S10, Android 11, so if you're using a different Android device or version, make sure to Google for the "deltas".
- The requirements for running a PWA are very restricting when it comes to HTTPS and local development. Quasar makes it possible to use HTTPS for local development out-of-the-box. Unforuneately, the HTTPS trick works for https://localhost, so how can an Android device access this "local network address" and load the PWA? I have a way to mask
localhost
with a desired domain so it'll behttps://test.meirg.co.il
, but that still doesn't solve the problem of making it accesible to other devices on the local network. - Let's get to business
- NodeJS v14.17.0+
- yarn
npm install --global yarn && \ yarn --version
- quasar cli - see yarn global if you're using nodemon
Install NPM packages
yarn global add @quasar/cli
cd awesome-pwa && yarn install
- Google Chrome for viewing the PWA and controlling remote devices, such as the target Android device.
- (Optional) I'm using Visual Studio Code, which makes the whole development process very easy. Especially the linting and auto-formatting.
Instead of installing the above requirements and dnsmasq, you can use Docker to simplify the process.
- Docker build image
cd awesome-pwa docker build -t unfor19/awesome-pwa:dev --target dev .
- Docker run container - Replace FQDN and IP address according to local network IP address.
# Executes quasar dev --mode pwa # Waits for build process to complete and prints App URL # In the background - runs dnsmasq local DNS server docker run --rm -it \ -p 8080:8080 \ -p 443:443 \ -p 53:53/udp \ -v "$PWD":/usr/src/app unfor19/awesome-pwa:dev "meirg.co.il.test" "192.168.0.5"
Before you read along, this project is the final artifact of the below steps. You can clone/fork this project, or even generate a GitHub repository from this project, and simply move on to the Usage section.
- Generate a PWA project with Quasar
quasar create awesome-pwa
- Project name:
awesome-pwa
- Project product name:
Awesome PWA
- Project description:
Testing PWA on a physical Android device
- Author:
firstName lastName <email@address.com>
- Pick your CSS preprocessor:
Sass with SCSS syntax
- Check the features needed for your project:
ESLint
TypeScript
- make sure to select TypeScript with<space>
and only then hit<enter>
- Pick a component style:
Composition API
- Pick an ESLint preset:
Prettier
- Continue to install project dependencies after the project has been created?
Yes, use Yarn
- Project name:
- From now on the working directory should be awesome-pwa
- Add PWA to the project with
quasar mode add pwa
... App • Creating PWA source folder... App • Copying PWA icons to /public/icons/ (if they are not already there)... App • PWA support was added
- Run the application locally in hot-reload mode
quasar dev --mode pwa
- Browser should be automatically opened, serving http://localhost:8080/#/. This is the application running locally on your machine, and any code change will immediately be applied to the app.
- To make sure Service Workers are loaded properly, set the browser settings for the application:
- Navigate to http://localhost:8080/#/
- Open Chrome DevTools
- Application > Service Workers > Tick Bypass for network
IMPORTANT: When using Docker, follow only the instructions marked with 🐳.
-
We need a local DNS server to trick everyone on the local network to think that
https://meirg.co.il.test
is actually my local machine network address, which is192.168.0.5
at the moment of writing. -
I chose dnsmasq for the job, but I'm sure any other option is valid. On macOS, use brew to install dnsmasq. If you're on Windows, I suggest you use WSL2 (TODO: Add docs for WSL2)
brew install dnsmasq
-
🐳 Check your local machine network IP address
ipconfig getifaddr en0 # Mine is 192.168.0.5
-
Edit dnsmasq config, and add map a domain to your local network address
vim /opt/homebrew/etc/dnsmasq.conf
# Maps a local ".test" domain to the local network ip address of the current machine # Make sure to use ".test" as a suffix # Change "meirg.co.il" with your domain and "192.168.0.5" with your local network IP address address=/meirg.co.il.test/192.168.0.5
-
🐳 Map the local domain
meirg.co.il.test
to our local machine192.168.0.5
- macOS - Create the directory "test" under /etc/resolver, see https://vninja.net/2020/02/06/macos-custom-dns-resolvers/ - any domain under
*.test
will resolve to192.168.0.5
which issudo mkdir -p /etc/resolver/test
- WSL2/Linux - Edit
/etc/hosts
filemeirg.co.il.test 192.168.0.5
- macOS - Create the directory "test" under /etc/resolver, see https://vninja.net/2020/02/06/macos-custom-dns-resolvers/ - any domain under
-
Restart
dnsmasq
by stopping and starting it- macOS
sudo brew services stop dnsmasq sudo brew services start dnsmasq
- macOS
-
🐳 Flush (refresh) DNS
- macOS
sudo killall -HUP mDNSResponder
- macOS
-
🐳 Check your local DNS server
# This is what happens when you use the default DNS server dig meirg.co.il.test # returns a.root-servers.net. nstld.verisign-grs.com. 2021113002 1800 900 604800 86400 # And now via dnsmasq local DNS server dig meirg.co.il.test @192.168.0.5 # returns 192.168.0.5
Assuming quasar dev -m pwa
is running in the background.
Everything is already set, all you gotta' do is open Google Chrome and navigate to http://meirg.co.il.test:8080
Assuming quasar dev -m pwa
is running in the background.
All the following steps are done on the Android device.
- Set your Android Device DNS settings, so it will resolve use
192.168.0.5
as the DNS server.- Open WIFI settings and change the DHCP settings from Auto to Manual.
- Set DNS records to
192.168.0.5
- The local machine which is runningdnsmasq
local DNS server1.1.1.1
- Cloudflare DNS to enable internet access in casednsmasq
is not responding
- Open Google Chrome and navigate to http://meirg.co.il.test:8080, the PWA should be accessible and will reload upon changing the application's source code
- That's nice, though it's not why we're here for. Since the application is served via HTTP and not HTTPS, the app is not classified as PWA by the Android device. All the cool features of add-to-home-screen (A2HS) and push-notification won't be available until we set HTTPS.
The standard process for generating a CA certificate is demonstrated in the below diagram.
Image Source: https://www.ssl.com/faqs/what-is-a-certificate-authority/
- For local development purposes, we're playing both the Applicant and CA roles, this is why I created the "convinience script" called scripts/generate_ca.sh, which does the following:
- Creates the directory
awesome-pwa/.certs
, this directory should not be committed to this repo. - Generates the required files
rootCA.key
,rootCA.pem
,${FQDN}.crt
(per domain) and the converted format${FQDN}.der.crt
to be installed on the Android device. The script is based on this stackoverflow answer - Prints a
Usage
message
- Creates the directory
- Execute the "convinience script", scripts/generate_ca.sh, to generate the desired keys and certificates
# Replace domain name ./scripts/generate_ca.sh "meirg.co.il"
- The next step is to tell Quasar's
devServer
awesome-pwa/quasar.conf.js to serve HTTPS and use the generated CA certificate and rootCA key.devServer: { https: { cert: '.certs/meirg.co.il.test.crt', key: '.certs/rootCA.key', }, port: 443, open: false },
- The final step is to install the generated
meirg.co.il.test.crt
certificate on your local machine so it can trust the certificate that the PWA is using- macOS
sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" "awesome-pwa/.certs/meirg.co.il.test.crt"
-r trustRoot
to make the OS trust the certificate and the Certification Authurity (CA). Trusting the CA is required since it's a self-signed certificate, and ourrootCA
is considered as a non-trusted CA by the OS. If you install the certificatemeirg.co.il.test.crt
by double clicking it, it will be marked as "not trusted" by the OS, as therootCA
is not trusted by the OS. - macOS
- Open Chrome browser and navigate to https://meirg.co.il.test, the PWA should be served properly via HTTPS
Previously, we generated meirg.co.il.test.der.crt
, this file is the one that should be installed the Android Device.
-
Local Machine > Upload
awesome-pwa/.certs/meirg.co.il.test.der.crt
to Google Drive -
Android Device > Download
meirg.co.il.test.der.crt
from Google Drive -
Android Device > Settings > Search "CA Certificate" > Install anyway > Select and install
meirg.co.il.test.der.crt
from local storage -
Android Device > Open Chrome browser and navigate to https://meirg.co.il.test, the PWA should be served properly via HTTPS, see final-result
TIP: To view the installed certificates, in the settings, search for User certificates
- First, Configure your Android with Developer Options and Allow USB Debugging. To be on the safe-side, I also downloaded and installed Samsung Smart Switch which includes Samsung Galaxy drivers. At this point I'm not sure if the drivers are necessary, I'll need to uninstall them to find out (TODO: uninstall drivers and see if it affects the installation)
- Connect your Android device to the local machine with a USB cable
- macOS - I used my macOS charger cable to connect since that's only cable I got with TypeC to TypeC
- Open Chrome and navigate to Chrome's
chrome://inspect#devices
page, see Remote debug Android devices - (WIP) The Android device device should appear on the list, so click
inspect
to view the contents of the mobile phone, on the local machine's display. It's like using your Android device as an emulator, though stuff is happening for real.
- During the process I realized I can't use
test.meirg.co.il
, and I must usemeirg.co.il.test
, this is because I'm on macOS, I need to map all*.test
traffic via the local DNS server (dnsmasq), and the trick is to use/etc/resolver/test
to do that. On Linux/WSL2, or even Windows, it's way easier, you can simply change the/etc/hosts
file and that's it. - Remote debugging does not work on WIFI, even though I enabled it on my Android device, so I must use a USB cable to make it work. I wonder if I'm doing something wrong.
- I need to read/write a blog post about CA, I feel like this subject is still not 100% clear to me.
- The application is not 100% stable in hot-reload mode and I still need to figure out why.
All commands are invoked from the awesome-pwa
directory.
- Change dir
cd awesome-pwa
- Install depenendcies
yarn install
- Build PWA app
yarn build
- Build SPA app
yarn build:spa
DNS_PROBE_FINISHED_NXDOMAIN
- The client cannot resolve DNS entrymeirg.co.il.test
; The local DNS server,dnsmasq
, is not set in the client's device. Fix by runningdnsmasq
and setting the DNS server records of the client properly# Run dnsmasq sudo brew services start dnsmasq # Set DNS server in client device, for example, 192.168.0.5
ERR_CONNECTION_REFUSED
- Client resolved DNS sodnsmasq
works, but the server is not online, fix by running localquasar dev
servercd awesome-pwa yarn serve
Your connection is not private
- Client doesn't have themeirg.co.il.crt
installed on the local machine, ormeirg.co.il.der.crt
installed on the Android device. Fix by installing the certificates as instructed in access-pwa-from-local-machine and access-pwa-from-an-android-device.
- https://developer.chrome.com/docs/devtools/progressive-web-apps/
- https://www.baeldung.com/openssl-self-signed-cert
- https://security.stackexchange.com/questions/20803/how-does-ssl-tls-work
- https://en.wikipedia.org/wiki/X.509
- https://www.digicert.com/kb/ssl-support/openssl-quick-reference-guide.htm
Created and maintained by Meir Gabay
This project is licensed under the CC Attribution 4.0 International License - see the LICENSE file for details