HTML5 Client Facilities

17 Mar 2020 - New York

Contents
  1. Browser Storage
    1. Contrasts with Cookies
    2. Local Storage and Session Storage
    3. IndexedDB
  2. Background Workers
    1. Dedicated vs Shared
    2. APIs available / not available
      1. Available
      2. Not available
    3. Demo: Syntax highlighting
  3. Progressive Web Apps
    1. Service worker
    2. Cache API
  4. WebSockets
    1. Demo
  5. Geolocation
    1. Location detection
      1. Methods
      2. Options: Accuracy
      3. Options: Timeout
      4. Options: Max Age
    2. Demo: Get current position
    3. Demo: Watch position

Browser Storage

Contrasts with Cookies

  • Web storage: Medium capacity, data stays on client, server can’t write to it
  • Cookies: Small capacity (4KB), data sent to server on each request, server can modify

Local Storage and Session Storage

  • Security: Sandboxed data, stored indexed by domain
  • Moderate Capacity: 5-10MB (but can be user-preference-controlled, rule of thumb: 5MB)
  • Session Storage: Data only available during the lifespan of the current user session.
  • Only strings (serialize objects: JSON.serialize)

IndexedDB

  • In-browser document database
  • Transactional
  • Can be queried
  • Same origin policy
  • High Capacity: LRU, up to 50% of available disk space
  • No more than 20% for a single origin
  • Use a library on top of it. e.g. PouchDB

Background Workers

Dedicated vs Shared

  • Dedicated: only available to the thread that spawned it
  • Shared: Available to any thread for its domain

APIs available / not available

Available

  • worker.postMessage
  • navigator (appName, appVersion)
  • platform (userAgent)
  • Timers
  • XmlHttpRequest
  • importScript for importing scripts
  • web_worker.terminate()

Not available

  • DOM access
  • UI related members of window
  • parent global scope
  • web storage

Demo: Syntax highlighting

// host page
// hostWorker: link to the bg thread
const scriptToExecute = 'worker.js'
const hostWorker = new Worker(scriptToExecute)
hostWorker.onmessage = ({data}) => {
  if (data.markup) {
    // inject into the DOM the markup returned from bg thread
    demo.$result.innerHTML = data.markup;
  }
}

// initiate bg job by posting message
hostWorker.postMessage({ command: 'start.syntaxHighlighting' })


// import highlight.js
importScripts('/scripts/highlightjs/highlight.pack.js')
// highlight.js run in `self` scope

const worker = {
  message: async e => {
    try {
      const response = await fetch('message.js')
      const data = await response.text()
      const code = ['// from worker', data].join("\n")
      // `self` is a ref to the worker's global context
      const result = self.hljs.highlightAuto(code)
      postMessage({ message: 'done', markup: result.value })
    } catch (ex) {
      postMessage({ type: 'error', message: ex })
    }
  }
}

// listen for events and handle when emitted
addEventListener('message', worker.message)

Progressive Web Apps

Service worker

  • A proxy server that sits between web apps, the browser, and the network.
  • For design patterns, see the Service Worker Cookbook.
  • Lifecycle states: Installing, Activated, Idle, Terminated, Fetch/Message, Error
  • Storage: use IndexedDB
  • Integrated with fetch + cache

Cache API

const cache = await window.caches.open(CACHE_NAME)
cache.put(url, resp.clone())

const keys = await cache.keys()
keys.forEach(request => console.log(request.url))

const match = await cache.match(url)
await cache.delete(url)

WebSockets

  • Full duplex client/server communication
  • Polling doesn’t scale well. Socket header 2B, compared to ~100B for HTTP payloads.
  • See: HTML5 WebSocket: A Quantum Leap in Scalability for the Web

Demo

Dependencies: http-server, ws

// client
const ws = new WebSocket("ws://127.0.0.1:8181")
ws.onopen = event => {
  ws.send(`[${event.timeStamp}] Hello from the client!`)
}
ws.onmessage = message => {
  console.log(`Client: ${message.data}`)
}

// server
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8181 })

wss.on('connection', ws => {
  ws.on('message', message => {
    console.log(`Received: ${message}`)
    ws.send(`Server: ${message}`)
  })

  setTimeout(() => {
    ws.send('Hi again! (after 3 seconds)')
  }, 3000)
})

Geolocation

Location detection

Methods

  • Persistent or one-time location
  • IP address (always available, low accuracy, high processing overhead)
  • GPS (high accuracy, but long operation and not great indoors)
  • WiFi (accurate, quick + cheap response, but less robust)
  • cell tower triangulation (accurate, works indoors, quick & cheap, but requires signal and phone)
  • user-defined (most reliable but needs user input, can become stale)

Options: Accuracy

  • attempts to gather more accurate location
  • boolean (default false)
  • may not be effective

Options: Timeout

  • max time to calculate location (default is no limit)
  • defined in milliseconds

Options: Max Age

  • Used to determine refresh (default is 0ms, immediate refresh)

Demo: Get current position

navigator.geolocation.getCurrentPosition({
  position => {
    console.log(position)
    console.log(position.coords.latitude)
    console.log(position.coords.longitude)
    console.log(position.coords.accuracy)
  },
  error => {
    console.log(error)

    switch(error.code) {
    case 0:
      console.log('Unknown error')
      break
    case 1:
      console.log('Permission denied')
      break
    case 2:
      console.log('Position unavailable')
      break
    case 3:
      console.log('Timeout reached')
      break
    }
  }
})

Demo: Watch position

let watchID = null

function watchPosition() => {
  watchID = navigator.geolocation.watchPosition(
    position => {
      console.log(position)
    },
    error => {
      console.log(error)
    },
    {
      enableHighAccuracy: true, // default false
      timeout: 10000,   // 10s, default noone
      maximumAge: 2000, // 2s, default 0
    }
  )
}

function stop() => {
  navigator.geolocation.clearWatch(watchID)
}