/pwa-quasar-local

This project demonstrates how to develop a Progressive Web Application (PWA) locally on an Android device, using the Quasar Framework v2.

Primary LanguageJavaScriptCreative Commons Attribution 4.0 InternationalCC-BY-4.0

pwa-quasar-local

Push to Docker Registries Dockerhub pulls CC BY 4.0

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.

Final result

android-pwa-add-to-home-screen android-pwa-clicked-a2hs android-clicked-a2hs-install android-after-installing-pwa android-pwa-apps-list android-pwa-splashscreen android-pwa-loaded

Table Of Contents


Challenges

This is how it all happened - documenting my learning process for future me

  1. 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?
  2. 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".
  3. 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 be https://test.meirg.co.il, but that still doesn't solve the problem of making it accesible to other devices on the local network.
  4. Let's get to business

Requirements

Local Installation

  1. NodeJS v14.17.0+
  2. yarn
    npm install --global yarn && \
    yarn --version
  3. quasar cli - see yarn global if you're using nodemon
    yarn global add @quasar/cli
    Install NPM packages
    cd awesome-pwa && yarn install
  4. Google Chrome for viewing the PWA and controlling remote devices, such as the target Android device.
  5. (Optional) I'm using Visual Studio Code, which makes the whole development process very easy. Especially the linting and auto-formatting.

Docker

Instead of installing the above requirements and dnsmasq, you can use Docker to simplify the process.

  1. Docker build image
    cd awesome-pwa
    docker build -t unfor19/awesome-pwa:dev --target dev .
  2. 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"

PWA with hot-reload via HTTP

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.

  1. Generate a PWA project with Quasar
    quasar create awesome-pwa
    1. Project name: awesome-pwa
    2. Project product name: Awesome PWA
    3. Project description: Testing PWA on a physical Android device
    4. Author: firstName lastName <email@address.com>
    5. Pick your CSS preprocessor: Sass with SCSS syntax
    6. Check the features needed for your project:
      1. ESLint
      2. TypeScript - make sure to select TypeScript with <space> and only then hit <enter>
    7. Pick a component style: Composition API
    8. Pick an ESLint preset: Prettier
    9. Continue to install project dependencies after the project has been created? Yes, use Yarn
  2. From now on the working directory should be awesome-pwa
  3. 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
    
  4. Run the application locally in hot-reload mode
    quasar dev --mode pwa
  5. 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.
  6. To make sure Service Workers are loaded properly, set the browser settings for the application:
    1. Navigate to http://localhost:8080/#/
    2. Open Chrome DevTools
    3. Application > Service Workers > Tick Bypass for network

Setup A Local DNS Server

IMPORTANT: When using Docker, follow only the instructions marked with 🐳.

  1. 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 is 192.168.0.5 at the moment of writing.

  2. 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
    
  3. 🐳 Check your local machine network IP address

    ipconfig getifaddr en0
    # Mine is 192.168.0.5
  4. 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
  5. 🐳 Map the local domain meirg.co.il.test to our local machine 192.168.0.5

  6. Restart dnsmasq by stopping and starting it

    • macOS
      sudo brew services stop dnsmasq
      sudo brew services start dnsmasq
  7. 🐳 Flush (refresh) DNS

    • macOS
      sudo killall -HUP mDNSResponder
      
  8. 🐳 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

Access PWA From Local Machine

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


Access PWA From An Android Device

Assuming quasar dev -m pwa is running in the background.

All the following steps are done on the Android device.

  1. Set your Android Device DNS settings, so it will resolve use 192.168.0.5 as the DNS server.
    1. Open WIFI settings and change the DHCP settings from Auto to Manual.
    2. Set DNS records to
      1. 192.168.0.5 - The local machine which is running dnsmasq local DNS server
      2. 1.1.1.1 - Cloudflare DNS to enable internet access in case dnsmasq is not responding
    android-set-dns-records
  2. 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
  3. 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.

Set An HTTPS Connection From Local Machine To PWA

The standard process for generating a CA certificate is demonstrated in the below diagram.

ca-diagram

Image Source: https://www.ssl.com/faqs/what-is-a-certificate-authority/

  1. 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:
    1. Creates the directory awesome-pwa/.certs, this directory should not be committed to this repo.
    2. 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
    3. Prints a Usage message
  2. 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"
  3. 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
     },
  4. 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"
      
    IMPORTANT: The certificate must be installed with -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 our rootCA is considered as a non-trusted CA by the OS. If you install the certificate meirg.co.il.test.crt by double clicking it, it will be marked as "not trusted" by the OS, as the rootCA is not trusted by the OS.
  5. Open Chrome browser and navigate to https://meirg.co.il.test, the PWA should be served properly via HTTPS

Set An HTTPS Connection From An Android Device To PWA

Previously, we generated meirg.co.il.test.der.crt, this file is the one that should be installed the Android Device.

  1. Local Machine > Upload awesome-pwa/.certs/meirg.co.il.test.der.crt to Google Drive

  2. Android Device > Download meirg.co.il.test.der.crt from Google Drive

  3. Android Device > Settings > Search "CA Certificate" > Install anyway > Select and install meirg.co.il.test.der.crt from local storage

    android-install-ca-anyway android-select-ca android-installed-ca-certificate
  4. 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


Controlling The Android Device With Google Chrome

  1. 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)
  2. 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
  3. Open Chrome and navigate to Chrome's chrome://inspect#devices page, see Remote debug Android devices
  4. (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.

Conclusions

  1. During the process I realized I can't use test.meirg.co.il, and I must use meirg.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.
  2. 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.
  3. I need to read/write a blog post about CA, I feel like this subject is still not 100% clear to me.
  4. The application is not 100% stable in hot-reload mode and I still need to figure out why.

Local Development

All commands are invoked from the awesome-pwa directory.

  1. Change dir
    cd awesome-pwa
  2. Install depenendcies
    yarn install
  3. Build PWA app
    yarn build
  4. Build SPA app
    yarn build:spa

Troubleshooting

  • DNS_PROBE_FINISHED_NXDOMAIN - The client cannot resolve DNS entry meirg.co.il.test; The local DNS server, dnsmasq, is not set in the client's device. Fix by running dnsmasq 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 so dnsmasq works, but the server is not online, fix by running local quasar dev server
    cd awesome-pwa
    yarn serve
  • Your connection is not private - Client doesn't have the meirg.co.il.crt installed on the local machine, or meirg.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.

Useful Resources


Authors

Created and maintained by Meir Gabay

License

This project is licensed under the CC Attribution 4.0 International License - see the LICENSE file for details