BOM APIs: WebWorker (Worker) & ServiceWorkers

  1. WebWorker (can)
    1. Start another Thread to execute JavaScript
  2. ServiceWorkers (can do)
    1. Start another Thread and execute JavaScript on the background
    2. Push Notification
    3. Background Sync
    4. Offline Experience
  3. Resources
    1. Cookbooks: awesome-service-workers & serviceworke.rs
    2. Playground: Notification Generator
  4. Annotated Examples
    Web Workers or Service Workers don’t have access of the DOM.

    // for WebWorkers
    // which could only react to data sent specifically to them
    
    if(window.Worker){
      let myWorker = new Worker("worker.js")
    
      // sending objects to myWorker's scope
      myWorker.postMessage("worker takes in object")
    
      // handing the return value from myWorker async
      myWorker.onmessage = function(e){
        handingTheResult(e.data)
      }
    }
    // for ServiceWorkers
    // which mainly react to external (network) events
    // YOUR_MAIN_JS_FILE.js
    
    if(navigator.serviceWorker){
      // the worker can control any URL on the same domain
      // that starts with, for example, "/js/"
      // or we could specify it with the second param (param.scope)
      navigator.serviceWorker.register("/js/sw.js", { scope: "/ServiceWorkerScope"})
        .then(function(registration){
          // it's async
      })
        .catch(function(err){
          // console.log(err)
          // navigator.sendBeacon("/err", err) 
      })
    }
    // inside of the sw.js file
    
    const CACHE_VER = 20170926
    let filesToCache = [
        "/css/userMayNeedIt.css"
        , "/js/userMayNeedIt.js"
    ]
    // the global object in the sw.js's scope is "self"
    // as window is a DOM object 
    self.addEventListener("install", (e) => {
      // have fun using ES6 here
      // as ES6 is better supported than ServiceWorker
      // 
      // we could cache an entire request here!
      //
      // to avoid hogging resources
      e.waitUntil(
        // use "CacheStorage" API by caches
        // caches.open() will create a new cache
        caches.open(CACHE_VER)
        .then((cache) => {
          console.log("cache opened")
          // adding files to the newly opened cache
          return cache.addAll(filesToCache)
        })
      )
    })
    
    // handling the XMLHttpRequest offline if possible
    self.addEventListener("fetch", e => {
      e.respondWith(
        cache.match(e.request)
          .then(res => {
            // answer the request locally if possible
            if(res){ return res }
            // if not, let's fetch
            return fetch(e.request).then(res => {
              // overwrite or create a new entry on cache
              // res.clone() allows us to handle stream, say
              // caching the starting part of a 1080P movie
              cache.put(e.request, res.clone())
              return res
            })
          }
        })
      )
    })

    another pattern for fetch handling

    // inside of the sw.js file
    self.addEventListener("fetch", e => {
      // respond to the fetch immediately
      e.respondWith(cache.match(e.request)
        .then(res => res))
    
      // starting a new fetch request
      e.waitUntil(
        fetch(e.request)
        .then(res => {
          cache.put(e.request, e.clone())
          .then(() => res)
          .then(reFetchCallback)
        })
      )
    })
    
    function reFetchCallback(){
      // the serviceWorker is not linked to any opened tab or domain
      // so, we have to check on each tabs
      // also, the serviceWorker could serve multiple tabs
      return self.clients.matchAll().then(tabs => {
        tabs.forEach(tab => {
          let message = {
            // this object is not standard
            type: "refresh"
            , url: response.url
            , eTag: response.headers.get("ETag")
          }
          tab.postMessage(JSON.stringify(message))
        })
      })
    }
    // inside of the main.js
    if(navigator.serviceWorker){
      navigator.serviceWorker.register("/js/sw.js", { scope: "/ServiceWorkerScope"})
    
      // listening for messages from serviceWorker
      navigator.serviceWorker.onmessage = function(e){
        let message = JSON.parse(e.data)
        let lastETag = localStorage.currentETag
        if(lastETag !== message.eTag){
          // the content are updated
        }
      }
    }

    Example of Push Notification

    self.addEventListener("push", e => {
      let push = e.data.json()
      // let push = e.data.text()
    
      e.waitUntil(){
        self.registration
        .showNotification(push.title, 
          {
            body: push.body
            , icon: "/path/to/icon.jpg"
            // actions are buttons
            , actions: {
              action: true, title: "OK"
              , action: false, title: "Cancel"
            }
          }
        )
      }
    })

    via Service Workers – Patrick Kettner

BOM APIs: deviceproximity & userproximity

// 挺好玩的,就是想不到怎么利用这些数据,
// 或许给小孩子表 演一个魔术?
// 不同设备采样的传感器位置不一,
// 多数在(主)屏幕一面
window.addEventListener("deviceproximity", function(e){
  // your imagination ... 
  document.body.innerHTML = `
  DeviceProximityEvent.max: ${e.max}
  DeviceProximityEvent.min: ${e.min}
  DeviceProximityEvent.value: ${e.value}
  `
})
// 同上
window.addEventListener("userproximity", function(e){
  // your imagination ...
  if(e.near){
    alert("fun fact:手机屏幕上有很多细菌")
  }
})

类似的API还有:DeviceMotionEvent

BOM APIs: window.DeviceLightEvent

// 网页应用可以根据(手机)用户当前的环境光亮度,
// 来相应地执行一些任务。
// 比如,根据如果用户所在环境光线比较暗,将网页切换至夜间模式
if(window.DeviceLightEvent){
  window.addEventListener("devicelight", function(aDeviceLightEvent){
    let currentAmbientLightInLux = aDeviceLightEvent.value
    // your imagination ... 
  })
}else{
  // API Not Available
}
window.addEventListener("devicelight", function(aDeviceLightEvent){
  let currentAmbientLightInLux = aDeviceLightEvent.value
  // your imagination ... 
})
window.ondevicelight = function(aDeviceLightEvent){
  let currentAmbientLightInLux = aDeviceLightEvent.value
}

BOM APIs: navigator.getBattery()

if (navigator.battery 
 || navigator.webkitBattery 
 || navigator.mozBattery 
 || navigator.msBattery) {
  // 老API
}else if(navigator.getBattery()){
  navigator.getBattery().then(function(bat){
    // 新API
    console.log(bat)
  })
}else{
  // 无API
}

前端跨域数据交换的常见方案摘要

  1. JSONP
    // 用户访问域名A,域名A下有.js文件包含以下代码
    let jsonpDemo = function(data){
      console.log(data.id === 1)
    }
    // 用户下域名B的.js文件(这样可以规避同源策略),并运行它
    jsonpDemo({"id": 1, "name": evan}) // true
  2. document.domain + iframe(仅适用于主域相同,子域不同的情景)
    // main domain page, say "main.com/index.html"
    <iframe src="https://sub.main.com/iframe.html"></iframe>
    <script>
      document.domain = "main.com"
      let data = "hello world"
    </script>
    // sub domain page, AKA, "sub.main.com/iframe.html"
    <script>
      document.domain = "main.com"
      console.log(window.parent.data) // hello world
    </script>
  3. location.hash + iframe:略
  4. window.name + iframe:略
  5. postMessage API
    数据的传输发生在本地。

    foo.com/foo.html
    <iframe id="iframe" src="http://www.bar.com/bar.html" style="display: none"></iframe>
    
    <script>
      let iframe = document.querySelector("#iframe")
      iframe.onload = function(){
        let data = { "id": 1, "msg": "Hello World" }
        // foo.com向bar.com传输数据
        // 部分浏览器只支持string类数据,因此将对象stringify一下
        iframe.contentWindow.postMessage(JSON.stringify(data)
        , "http://www.bar.com")
      }
      // 监听来自bar.com的数据
      window.addEventListener("message", function(e){
        alert(`data from bar.com: ${e.data}`)
      })
    </script>
    // bar.com/bar.html
    <script>
      window.addEventListener("message", function(e){
        let receivedData = JSON.parse(e.data)
        if(receivedData.msg === "Hello World"){
          window.parent.postMessage("Hello To You Too", "http://www.foo.com")
        }
      })
    </script>
  6. CORS || nginx代理 || Node.js中间件
    // 前端的原生ajax请求一般需要开启带cookies
    let ajax = new XMLHttpRequest()
    ajax.withCredentials = true
    
    // 其他的是后端的事情了
  7. Socket.io(WebSocket API):略
    WebSocket的其他fallbacks:Adobe Flash Socket、ActiveX HTMLFile (IE)
  8. 非跨域通讯:SharedWorker

via 前端常见跨域解决方案(全)

BOM APIs: navigator.geolocation

// 处于信息安全的考虑,geolocation API只能在
// HTTPS连接下使用。
//
// 多数浏览器默认是通过Wi-Fi信息来确定用户的位置的,
// 主要是因为:1)使用GPS模块会额外好点;
// 2)GPS收星时间较长。
// 
// 如果只要GPS来确定位置,.getCurrentPosition()的
// 第三个参数需为" { highAccuracy: true } "
// 这种做法适用于用户在高速移动(行驶的轿车内)的情景
window.onload = function(){
  if(navigator.geolocation){
    navigator.geolocation.getCurrentPosition(onPos, onErr);
  }else{ console.log(`geolocation not available`); }
  
  function onPos(pos){ 
    let lat = pos.coords.latitude;
    let long = pos.coords.longtutide;
    // your code here
  }
  function onErr(err){ console.log(err.message); }
}