Maxim Salnikov

@webmaxru

Native Web Apps:

Are We There Yet?

How do we want web apps to feel like

and what tools do we have to achieve it

Maxim Salnikov

  • PWA Summit co-organizer

  • PWA Oslo / PWA London meetups organizer

  • Google Dev Expert in Web Tech / Capabilities & Installability

Developer Audience Lead at Microsoft

1989

A “web” of “hypertext documents” could be viewed by “browsers

2023

  • Historically depends on the "connection status"

  • Works within the browser context, not underlying platform

  • Evergreen browsers

  • Excellent tooling

  • Huge community

Web Almanac 2022

Requirements

  • Security & privacy

  • Performance

  • Persistence

  • Nativeness

Native-like...

  • Installation, configuration, launch

  • Windowing

  • Outbound integrations

  • Inbound integrations

  • Notifications

  • Background tasks

  • Access to hardware

Demo app

  • Link to GitHub repo with source code
  • Many extra and extended features
  • How to host in the cloud for free

edge://flags

We want to install, uninstall, configure app and launch it in different ways

  • Installation prompts

  • App hubs

  • Metadata update dialogues

Installation

Launch

  • Run on user login

  • Shortcuts

  • Launch mode

Installation criteria

Web App Manifest

Service Worker with "fetch"

{

{
  "name": "BPM Techno",
  "short_name": "BPM Techno Counter",
  "start_url": "?utm_source=homescreen",
  "display": "standalone",
  "background_color": "#fff",
  "description": "A free online BPM counter",
  "icons": [{
    "src": "images/touch/48x48.png",
    "sizes": "48x48",
    "type": "image/png"
  }]
}

app.webmanifest

Shortcuts

"shortcuts": [
  {
    "name": "Upload MP3 File",
    "short_name": "Upload MP3r",
    "description": "Count BPM of the uploaded file",
    "url": "/upload-mp3?utm_source=homescreen",
    "icons": [{ "src": "/icon-mp3.png", "sizes": "192x192" }]
  }
]

app.webmanifest

Launch mode

"launch_handler": {
  "client_mode": "auto" | "navigate-new" | "navigate-existing" | "focus-existing"
}

app.webmanifest

launchQueue.setConsumer(launchParams => {
  const url = launchParams.targetURL;
});

main.js

We want to follow UX principles of the platform and have control over every pixel

Windowing

  • Main window mode

  • Title bar options

  • Tabbed experience

{
  "display": "fullscreen" | "standalone" |
    "minimal-ui" | "browser",
  "display_override": ["window-control-overlay",
    "minimal-ui"],
}

app.webmanifest

Windowing

  • Main window mode

  • Title bar options

  • Tabbed experience

titlebar-area-x
titlebar-area-y
titlebar-area-width
titlebar-area-height

CSS Variables

navigator.windowControlsOverlay.
  getBoundingClientRect()

navigator.windowControlsOverlay.visible

JavaScript API

Windowing

  • Main window mode

  • Title bar options

  • Tabbed experience

{
  "display_override": "tabbed"
}

app.webmanifest

We want to use all available interfaces of the platform

Outbound integrations

  • Web Share API

  • Contact Picker API

  • File Access API

if (navigator.share) {
  navigator.share({
    title: 'BPM Techno',
    text: 'Check out BPMTech.no',
    url: 'https://bpmtech.no',
  })
    .then(() => console.log('Successful share'))
    .catch((err) => console.error(err));
}

main.js

Outbound integrations

  • Web Share API

  • Contact Picker API

  • File Access API

const props = ['name', 'email', 'tel'];
const opts = {multiple: true};

try {
  const contacts =
    await navigator.contacts.select(props, opts);
  handleResults(contacts);
} catch (err) {
  console.error(err));
}

main.js

Outbound integrations

  • Web Share API

  • Contact Picker API

  • File Access API

let openFileHandle;
btnOpen.addEventListener('click', async () => {
  [openFileHandle] =
    await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
});

main.js

const saveFileHandle =
  await window.showSaveFilePicker();
const directoryHandle = 
  await window.showDirectoryPicker();

We want to platform to invoke the app in different ways

Inbound integrations

  • URL handlers

  • Declarative Link Capturing

  • Protocol handlers

  • File type handlers

  • Share target

"url_handlers": [
  {
    "origin": "https://bpmtech.no"
  },
  {
    "origin": "https://partnerapp.com"
  }
]

app.webmanifest

"web_apps": [
  {
    "manifest": "https://partnerapp.com/manifest.json",
    "details": {
      "paths": [
        "/public/data/*"
      ]
    }
  }
]

.well-known/web-app-origin-association

Inbound integrations

  • URL handlers

  • Declarative Link Capturing

  • Protocol handlers

  • File type handlers

  • Share target

"capture_links":
  "none" | "new-client" |
    "existing-client-navigate"

app.webmanifest

Inbound integrations

  • URL handlers

  • Declarative Link Capturing

  • Protocol handlers

  • File type handlers

  • Share target

"protocol_handlers": [
  {
    "protocol": "web+bpm",
    "url": "index.html?bpm=%s"
  }
]

app.webmanifest

web+bpm://bpm=120

Inbound integrations

  • URL handlers

  • Declarative Link Capturing

  • Protocol handlers

  • File type handlers

  • Share target

"file_handlers": [
  {
    "action": "/open-mp3",
    "accept": { "audio/mpeg": [".mp3"] },
    "icons": [
      {
        "src": "./images/mp3-file.png",
        "sizes": "144x144"
      }
    ]
  }
]

app.webmanifest

window.launchQueue.setConsumer(launchParams => {
  const fileHandle = launchParams.files[0];
});

main.js

Inbound integrations

  • URL handlers

  • Declarative Link Capturing

  • Protocol handlers

  • File type handlers

  • Share target

"share_target": {
  "action": "/share",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url"
  }
}

app.webmanifest

window.addEventListener('DOMContentLoaded', ()=> {
  const parsedUrl = new URL(window.location);
  const url = parsedUrl.searchParams.get('url');
});

main.js

We want to keep user always updated

Badging

navigator.setAppBadge(42).catch((err) => {
  console.error(err)
});

navigator.clearAppBadge().catch((err) => {
  console.error(err)
});

main.js

#30DaysOfPWA

Web Push

Is coming to iOS / iPadOS devices this year!

We want to run some parts of the app in the background

Background tasks

  • Background Sync

  • Periodic BG Sync

  • Background Fetch

  • Payment Handler

We want to have direct access to the hardware

Access to hardware

  • Audio & Video Capture
  • Geolocation
  • Web Bluetooth
  • Web MIDI API
  • Magnetometer API
  • Web NFC API
  • Device Memory API
  • Network Information API
  • Battery Status API
  • Ambient Light Sensor
  • Proximity Sensor
  • WebHID
  • Serial API
  • Web USB
  • User Idle Detection

Where to follow?

🐡

How to experiment at scale?

Native Web: Are We There?

  • Installation, configuration, launch

  • Windowing

  • Outbound integrations

  • Inbound integrations

  • Notifications

  • Background tasks

  • Access to hardware

Thank you!

@webmaxru

Let's stay connected

Questions?

@webmaxru

Maxim Salnikov

Where to host your native web app?

...and visit Microsoft booth :)

Learn about web frontend, get prizes

Native Web Apps: Are We There Yet?

By Maxim Salnikov

Native Web Apps: Are We There Yet?

There are so many discussions about web VS native apps. Will we get to the point where the Web becomes truly Native for the majority of the platforms? Progressive Web Apps, Project Fugu, WebAssembly & other technologies actively contribute to moving in this direction. In this session, we go through a list of details that make the integration of an application & operating system seamless, and map it to the APIs available for the web platform. With the knowledge of what's available today and what's coming soon, you are empowered to build truly Native Web Apps to deliver the best user experience!

  • 280