HTML5 Client Facilities
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 scriptsweb_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)
}