Seven Awesome PWA Features

Progressive Web Apps (PWA) represent a collection of capabilities that put web apps on a level playing field with native iOS, Android, and desktops apps. The following tutorial implements a 7 lesser-known web features.

1. App Shortcuts

App Shortcuts

file_type_config manifest.json
{
                          "name": "Fireship",
                          //...
                          "shortcuts": [
                            {
                              "name": "Activity Feed",
                              "short_name": "Feed",
                              "description": "View your activity feed",
                              "url": "/feed?utm_source=homescreen",
                              "icons": [{ "src": "/icons/feed.png", "sizes": "192x192" }]
                            },
                            {
                              "name": "Recent Comments",
                              "short_name": "Comments",
                              "description": "View recent comments",
                              "url": "/comments?utm_source=homescreen",
                              "icons": [{ "src": "/icons/comments.png", "sizes": "192x192" }]
                            }
                          ]
                      }
                      

2. Contact Picker

The Contact Picker requires an HTTPS connection. If developing locally, use Ngrok to setup an SSH tunnel that can broadcast localhost to a secure URL.

file_type_js_official app.js
const btn = document.getElementById('contacts');
                      btn.addEventListener('click', (event) => getContacts());
                      
                      const props = ['name', 'email', 'tel', 'address', 'icon'];
                      const opts = {multiple: true};
                      const supported = ('contacts' in navigator && 'ContactsManager' in window);
                      
                      async function getContacts() {
                      
                          if (supported) {
                              const contacts = await navigator.contacts.select(props, opts);
                              console.log(contacts);
                          } else {
                              alert('contact picker not supported!')
                          }
                      
                      }
                      

3. Device Motion

Device Motion

file_type_js_official app.js
window.addEventListener('devicemotion', function(event) {
                          const el = document.getElementById('motion');
                          console.log(event);
                          el.innerText = (Math.round((event.acceleration.x + Number.EPSILON) * 100) / 100) + ' m/s2';
                          // el.innerText = event.rotationRate.beta;
                        });
                      
                        window.navigator.geolocation.getCurrentPosition(console.log)
                      

4. Bluetooth & External Devices

Bluetooth

file_type_js_official app.js
const button = document.getElementById('ble');
                      button.addEventListener('click', (event) => connectBluetooth());
                      
                      async function connectBluetooth() {
                      
                          // Connect Device
                          const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] });
                          const server = await device.gatt.connect();
                      
                          // Get heart rate data
                          const hr = await server.getPrimaryService('heart_rate');
                          const hrMeasurement = await hr.getCharacteristic('heart_rate_measurement');
                      
                          // Listen to changes on device
                          await hrMeasurement.startNotifications(); 
                      
                          hrMeasurement.addEventListener('characteristicvaluechanged', (e) => {
                              console.log(parseHeartRate(e.target.value));
                          });
                      
                      }
                      

5. Idle Detection

Idle Detection

file_type_js_official app.js
if ('IdleDetector' in window) {
                      
                          const idleBtn = document.getElementById('idle');
                          idleBtn.addEventListener('click', (event) => runIdleDetection());
                      
                      
                          async function runIdleDetection() {
                              const state = await IdleDetector.requestPermission();
                              console.log(state);
                      
                              const idleDetector = new IdleDetector();
                      
                              idleDetector.addEventListener('change', () => {
                                  const { userState, screenState } = idleDetector;
                                  console.log(idleDetector)
                              
                                  if (userState == 'idle') {
                                      // update database with status
                                  }
                              });
                      
                              await idleDetector.start({
                                  threshold: 120000,
                              });
                          }
                      }
                      

6. File System

File System

file_type_js_official app.js
const getFileBtn = document.getElementById('fs-get')
                      
                      getFileBtn.onclick = async () => {
                          const [handle] = await window.showOpenFilePicker();
                          const file = await handle.getFile();
                          console.log(file)
                      };
                      
                      
                      const saveFileBtn = document.getElementById('fs-save')
                      
                      saveFileBtn.onclick = async () => {
                        const textFile = new File(["hello world"], "hello.txt", {
                          type: "text/plain",
                        });
                        const handle = await window.showSaveFilePicker();
                        const writable = await handle.createWritable();
                        await writable.write(textFile);
                        await writable.close();
                      };
                      

7. Web Share

Use the Share Target API by updating the manifest with a share target:

file_type_config manifest.json
{
                          "name": "Fireship",
                          //...
                          "share_target": {
                            "action": "/share-photo",
                            "method": "POST",
                            "enctype": "multipart/form-data",
                            "params": {
                              "title": "name",
                              "text": "description",
                              "url": "link",
                              "files": [
                                {
                                  "name": "photos",
                                  "accept": "image/png"
                                }
                              ]
                            }
                          },
                      }
                      

Use the Web Share API to open a share dialog on the native device:

file_type_js_official app.js

                      
                      const shareBtn = document.getElementById('share');
                      
                      shareBtn.onclick = async (filesArray) => {
                          if (navigator.canShare) {
                              navigator.share({
                                  url: 'https://fireship.io',
                                  title: 'PWAs are awesome!',
                                  text: 'I learned how to build a PWA today',
                              })
                          }
                      }