PWA小试牛刀

590 阅读4分钟

PWA小试牛刀

1.PWA简介

PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。

2.PWA特点

  • 1.无需考虑跨平台,只需要考虑浏览器兼容性;
  • 2.通过url访问,无需发布到应用商店;
  • 3.可以安装到手机主屏,生成应用图标;
  • 4.安全:通过HTTPS访问,保证了安全性;
  • 5.离线访问:Service worker;
  • 6.消息推送;

3.演示

1.新浪微博 2.掘金

4.PWA实现

PWA可实现Web App 添加至主屏、离线缓存、离线消息推送功能;

主要依赖于manifest.json和service worker(在项目中可写为一个名为SW.js的文件并引入项目)

5.manifest.json

manifest.json 中主要包含app的基本信息,比如名称(name)、图标(icons)、显示方式(display)等等,是web app能被以类似原生的方式安装、展示的必要配置。

manifest.json配置
{
    "name": "小日常--一个简洁的习惯管理工具",
    "short_name": "小日常",
    "icons": [{
            "src": "/img/icons/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/img/icons/android-chrome-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "start_url": "/",
    "display": "standalone",
    "background_color": "#fff",
    "theme_color": "#ffb95c",
}

新浪微博截图

6.1.Service Worker实现离线缓存

sw 的生命周期

它的生命周期有 3 个部分组成:install -> waiting -> activate。开发者常监听的生命周期是 install 和 activate

install 阶段

const VERSION = "v1";
self.addEventListener("install", event => {
    // ServiceWoker注册后,立即添加缓存文件,
    // 当缓存文件被添加完后,才从install -> waiting
    event.waitUntil(
        caches.open(VERSION).then(cache => {
            return cache.addAll([
            "./index.html",
            "./image.png"
            ]);
        })
    );
});

activate阶段

// 添加缓存
self.addEventListener("install", event => {
    // 跳过 waiting 状态,然后会直接进入 activate 阶段
    event.waitUntil(self.skipWaiting());
});

 // 缓存更新
self.addEventListener("activate", event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all([
                // 更新所有客户端 Service Worker
                self.clients.claim(),

                // 清理旧版本
                cacheNames.map(cacheName => {
                    // 如果当前版本和缓存版本不一样
                    if (cacheName !== VERSION) {
                        return caches.delete(cacheName);
                    }
                })
            ]);
        })
    );
});

fetch阶段

self.addEventListener("fetch", event => {
   event.respondWith(
       caches.match(event.request).then(response => {
           // 如果 Service Workder 有自己的返回
           if (response) {
               return response;
           }

           let request = event.request.clone();
           return fetch(request).then(httpRes => {
               // http请求的返回已被抓到,可以处置了。

               // 请求失败了,直接返回失败的结果就好了。。
               if (!httpRes || httpRes.status !== 200) {
                   return httpRes;
               }

               // 请求成功的话,将请求缓存起来。
               let responseClone = httpRes.clone();
               caches.open(VERSION).then(cache => {
                   cache.put(event.request, responseClone);
               });

               return httpRes;
           });
       })
   );
});

为什么用request.clone()和response.clone() 需要这么做是因为request和response是一个流,它只能消耗一次

6.2. Service Worker实现消息推送

发送Push过程

在浏览器端,注册一个Service Worker之后会返回一个注册的对象,调的pushManager.subscribe让浏览器弹框;

如果点击允许的话,浏览器就会向FCM请求生成一个subscription(订阅)的标志信息,然后把这个subscription发给服务端存起来,用来发Push给当前用户。服务端使用这个subscription的信息调web push提供的API向FCM发送消息,FCM再下发给对应的浏览器。然后浏览器会触发Service Worker的push事件,让Service Worker调showNotification显示这个push的内容。

操作系统就会显示这个Push

1.浏览器发起询问,生成subscription

在注册完service worker后,调用subscribe询问用户是否允许接收通知,如下代码所示:

navigator.serviceWorker.register("sw-4.js").then(function(reg){
    console.log("Yes, it did register service worker.");
    if (window.PushManager) {
        reg.pushManager.getSubscription().then(subscription => {
            // 如果用户没有订阅
            if (!subscription) {
                subscribeUser(reg);
            } else {
                console.log("You have subscribed our notification");
            }       
        });     
    }
}).catch(function(err) {
    console.log("No it didn't. This happened: ", err)

上面代码在发起订阅前先看一下之前已经有没有订阅过了,如果没有的话再发起订阅。发起订阅的subscribeUser实现如下代码所示

function subscribeUser(swRegistration) {
    const applicationServerPublicKey = "BBlY_5OeDkp2zl_Hx9jFxymKyK4kQKZdzoCoe0L5RqpiV2eK0t4zx-d3JPHlISZ0P1nQdSZsxuA5SRlDB0MZWLw";
    const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
    
    // subscribe 会给推送服务器发送一个网络请求
    swRegistration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: applicationServerKey
    })
    // 用户同意
    .then(function(subscription) {
        console.log('User is subscribed:', JSON.stringify(subscription));
        jQuery.post("/add-subscription.php", {subscription: JSON.stringify(subscription)}, function(result) {
            console.log(result);
        });
    })
    // 用户不同意或者生成失败
    .catch(function(err) {
        console.log('Failed to subscribe the user: ', err);
    });

2.发送推送

const webpush = require('web-push');
// 用谷歌账号申请一个fcm api key
let fcmAPIKey = "AIzaSyDIAaIMTZeVmEorOJVBxxxx";
let privateKey = "Wo8g-2YBCkDhMRPsvoxxxx";
 
webpush.setVapidDetails(
    'mailto:liyincheng101@gmail.com',
    publicKey,
    privateKey
);
 
// 从数据库取出用户的subsciption
const pushSubscription = {"endpoint":"https://fcm.googleapis.com/fcm/send/ci3-kIulf9A:APA91bEaQfDU8zuLSKpjzLfQ8121pNf3Rq7pjomSu4Vg-nMwLGfJSvkOUsJNCyYCOTZgmHDTu9I1xvI-dMVLZm1EgmEH0vDA7QFLjPKShG86W2zwX0IbtBPHEDLO0WgQ8OIhZ6yTnu-S","expirationTime":null,"keys":{"p256dh":"BAdAo6ldzRT5oCN8stqYRemoihPGOEJjrUDL6y8zhdA_swao_q-HlY_69mzIVobWX2MH02TzmtRWj_VeWUFMnXQ=","auth":"SS1PBnGwfMXjpJEfnoUIeQ=="}};
 
// push的数据
const payload = {
    title: '一篇新的文章',
    body: '点开看看吧',
    icon: '/html/app-manifest/logo_512.png',
    data: {url: "https://fed.renren.com"}
  //badge: '/html/app-manifest/logo_512.png'
};
 


3.接收推送

service worker监听push事件,将通知详情推送给用户

this.addEventListener('push', function(event) {
    let notificationData = event.data.json();
    const title = notificationData.title;
    
    // 可以发个消息通知页面
    //util.postMessage(notificationData); 
    // 弹消息框
    event.waitUntil(
       // 使用提供的信息来显示 Web 推送通知
          self.registration.showNotification(
          title, notificationData)
    );
});

7.总结

优势

  • 1.可以将app的快捷方式放置到桌面上,全屏运行,与原生app无异
  • 2能够在各种网络环境下使用,包括网络差和断网条件下,不会显示undefind
  • 3.推送消息的能力
  • 4.其本质是一个网页,没有原生app的各种启动条件,快速响应用户指令

问题

  • 1.各大厂商还未明确支持pwa
  • 2.国内使用率不高
  • 等等