PWA实战准备篇一

PWA实战准备篇

这一篇的内容是利用ngrok让我们的服务跑在https上,然后利用一个boilerplate快速搭建PWA的环境

让我们的Service Worker运行起来

在讲PWA拦截网络请求之前,我们先说一下如何让我们的
Service Worker能够运行起来,因为支持https,这里有几种办法

  • 使用github来托管代码
  • 使用localhost,但这种有局限性,有时候我们不太方便用localhost
  • 使用FireBase来托管代码
  • 使用ngrok来启动服务

这里主要介绍第四种办法,使用ngrok来启动服务,使用方法:

  • 下载ngrok https://dashboard.ngrok.com/get-started
  • 解压后安装,加入环境变量
  • 在我们静态资源的文件夹,启动http服务,例如我用的是http-server的一个库,默认端口是8080
    PWA实战准备篇一_第1张图片

  • 然后启动ngrok来进行转发,ngrok http 8080
    PWA实战准备篇一_第2张图片

能看到上面图片,在浏览器中访问https://1e517654.ngrok.io 就可以啦,手机也可以哟,不一定要在局域网

boilerplate 梯子

这里我们用了一个boilerplate,链接地址:https://github.com/vuejs-templates/pwa

我们先看一下目录
.
├── README.md
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── load-minified.js
**│ ├── service-worker-dev.js
│ ├── service-worker-prod.js**
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
**│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js**
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── dist
│ ├── index.html
│ ├── service-worker.js
│ └── static
│ ├── css
│ │ └── app.1d063bc0cd301699760e884e1e4c3379.css
│ ├── img
│ │ └── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── msapplication-icon-144x144.png
│ │ ├── mstile-150x150.png
│ │ └── safari-pinned-tab.svg
│ ├── js
│ │ ├── app.ba6cfb06f7fd7ba74300.js
│ │ ├── app.ba6cfb06f7fd7ba74300.js.map
│ │ ├── manifest.2ae2e69a05c33dfc65f8.js
│ │ ├── manifest.2ae2e69a05c33dfc65f8.js.map
│ │ ├── vendor.1134bd10c2663336acdb.js
│ │ └── vendor.1134bd10c2663336acdb.js.map
│ └── manifest.json
├── index.html
├── package.json
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── Hello.vue
│ ├── main.js
│ └── router
│ └── index.js
├── static
│ ├── img
│ │ └── icons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── msapplication-icon-144x144.png
│ │ ├── mstile-150x150.png
│ │ └── safari-pinned-tab.svg
└── manifest.json
└── yarn.lock

大部分的内容和我们之前用vue是一样的,这里主要是说一下和pwa相关的东西

但是说实话,如果是从学习pwa的角度,我们完全可以自己去搭环境,没必要用这个boilerplate。当然用它好处在于我们去关心业务逻辑,而不是让我们搭环境就搭个半天。

项目使用webpack来编译打包,同样我们的 Service Worker 也用了webpack的插件:sw-precache-webpack-plugin

我首先回忆一下之前的内容,我们如果需要缓存一个html和css或者说js,我们需要再sw.js中一个个的添加,这种操作对我们现代前端来说是不科学的,能不能有工具,让我们设置一个数组,他就自动写好了呢?

这个插件就是上面说的sw-precache-webpack-plugin

new SWPrecacheWebpackPlugin({
 cacheId: 'upchat-pwa',
 filename: 'service-worker.js',
 staticFileGlobs: ['dist/**/*.{js,html,css}'],
 minify: false,
 stripPrefix: 'dist/'
})

其中的staticFile Globs就是缓存需要的东西,其他还有很多选项,有兴趣大家可以去查看,通过这个插件会自动生成一个server-worker.js,里面就有我们之前写到的监听install、fetch的事件。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {  
    // 省略
    });
  )
});

然后我们还有一个mainfest.json,里面定义了web应用的图标、启动页面和样式

{
  "name": "upchat-pwa",
  "short_name": "U聊",
  "icons": [
    {
      "src": "/static/img/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/static/img/icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#000000",
  "theme_color": "#4DBA87"
}

有了fetch、版本控制、manifest,那么推送怎么办呢?我们U聊肯定是需要推动的,用户不可能一直在U聊的界面上,这个稍微复杂一点,且听我慢慢道来。

因为是推送,我们肯定是有推送的服务器,像ios有苹果的推送服务器,android上也有华为和小米的推送服务器。当然如果你想自己去写一个推送服务器也不是不可以,但是考虑到浏览器的多样性,这里我建议还是用的第三方SaaS产品,比如OneSignal、Roost和Aimtell都有成熟的解决方案。我们接下来就看如何把OneSignal集成到我们的boilerplate里面去。

在说集成之前,我们先回顾一下PWA中的推送通知的流程

首先浏览器会显示一个提示来询问用户是否愿意接受通知,如果接受,可以将用户订阅的详细信息保存在服务器上,稍后用来发送通知,因为这些信息对于每个用户、设备和浏览器都是唯一的,所以如果一个用户使用了多个设备登录了网站,那么每台设备都会提示用户是否接受通知。

一旦用户接受了通知,服务器就可以使用这些订阅的信息来向用户发送消息,并且使用一个调度任务来向用户分发实时信息。

我们来看一个实际的例子:

浏览器端的代码:


<html>
    <head>
        <meta charset="UTF-8">
        <title>test pwa pushtitle>
        <link rel="manifest" href="/manifest.json">
    head>
    <body>
        <script>
            var endpoint;
            var key;
            var authSecret;
            var vapidPublickey = '公钥和服务器约定';
            function urlBase64ToUint8Array(base64string) {
                const padding = '='.repeat((4 - base64String.length % 4) % 4);
                const base64 = (base64String + padding).replace(/\-/, '+').replace(/_/g, '/');
                const rawData = window.atob(base64);
                const outputArray = new Uint8Array(rawData.length);

                for(let i = 0; i < rawData.length; i++) {
                    outputArray[i] = rawData.charCodeAt(i);
                }
                return outputArray;
            }

            if ('serviceWorker' in navigator) {
                navigator.serviceWorker.register('sw.js').then(function(registration) {
                    return registration.pushManager.getSubscription()
                        .then(function(subscription) {
                            if (subscription) {
                                return;
                            }
                            return registration.pushManager.subscription({
                                userVisibileOnly: true,
                                applicationServerKey: urlBase64ToUint8Array(vapidPublickey)
                            })
                            .then(function(subscription) {
                                var rawKey = subscription.getKey? subscription.getKey('p256dh'): '';
                                key = rawKey? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))): '';
                                var rawAuthSecret = subscription.getKey? subscription.getKey('auth'): '';
                                authSecret = rawAuthSecret? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))): '';
                                endpoint = subscription.endpoint;
                                return fetch('./register', {
                                    method: 'post',
                                    headers: new Headers({
                                        'content-type': 'application-json'
                                    }),
                                    body: JSON.stringify({
                                        endpoint: endpoint,
                                        key: key,
                                        authSecret: authSecret,
                                    }),
                                })
                            })
                        });
                }).catch(function(err) {
                    console.log('注册失败')
                });
            }
        script>
    body>
html>

要发送推送通知,需要使用VAPID协议,VAPID协议是自主应用服务器的简称。本质上定义了应用服务器和推送服务器之间的握手,并允许推送服务确认那个站点正在发送消息。这是很重要的,因为这意味着应用服务器能够包含其自身的相关附加信息,这些信息可用于联系应用服务器的操作人员。

代码中有一个VAPID的公钥,这个公钥我们在服务器代码中详细解释

后续的代码就是注册Service Worker,然后检测用户是否订阅过了,如果订阅过了就需要再给服务器发送消息,否则用pushManager.subscribe函数来提示用户订阅,这个函数使用VAPID公钥识别,这个需要转换为UInt8Array,因为规范只支持这种类型。最后用POST请求发送给服务器

服务器代码:

const webpush = require('web-push');
const express = require('express');
var bodyParser = require('body-parser');
const app = express();


webpush.setVapidDetails(
    'mailto:[email protected]',
    '对应的公钥',
    '不清楚'
);

app.post('/register', function(req, res) {
    var endpoint = req.body.endpoint;
    // saveRegistrationDetails(endpoint, key, authSecret);
    const pushSubscription = {
        endpoint: endpoint,
        keys: {
            auth: req.body.authSecret,
            p256dh: req.body.key
        }
    };

    var body = 'Thank you';
    var iconUrl = 'https://ddd.png;';
    webpush.sendNotification(pushSubscription, 
        JSON.stringify({
            msg: body,
            url: 'http://localhost:3111',
            icon: iconUrl,
        })
    ).then(result => res.sendStatus(201))
    .catch(err => console.log(err));

});

app.listen(3111, function(){
    console.log('listen on 3111');
});

有了服务器代码之后我们需要再前端添加一点代码,让浏览器能够接受通知,并且能够操作

       self.addEventListenser('push', function(event) {
           var payload = event.data? JSON.parse(event.data.text()): 'no payload';
           var title = 'Progressive Times';
           event.waitUtil(
               self.registeration.showNotification(title, {
                   body: payload.msg,
                   url: payload.url,
                   icon: payload.icon
               });
           );
       });

       self.addEventListenser('notificationclick', function(event) {
           event.notification.close(); // 点击后关闭通知
           //检查当前窗口是否打开了,如果没有就切换到这个窗口
           event.waitUtil(
               clients.matchAll({
                   type: 'window'
               })
               .then(function(clientList) {
                   for(var i = 0; i < clientList.length; i++) {
                       var client = clientList[i];
                       if(client.url == '/' && 'focus' in client) {
                           return client.focus();
                       }
                       if (client.openWindow) {
                           return client.openWindow('http://localhost:3111') //单击后代开URL
                       }
                   }
               })
           );
       }); 

但是就算你们把上面的代码,写好了,还是会发现,返回的是500,因为国内的网络被墙了,在chrome中打开,后,pushManager.getSubscription()是不需要push Service信息的,也就是浏览器去哪个push service注册是浏览器内部定义好的,不是外部可以修改的。也就是说第三方push service永远无法给chrome、firefox这些已有push service的浏览器下发消息。

因为web push不仅仅需要业务服务器,还需要push中间服务器,chrome下就是fcm

在chrome下:

"endpoint": "https://fcm.googleapis.com/fcm/send/fBAoBL13tiw:APA91bGlEPl5USKrU9OurtYvS9ljEemAh1r6Hxr2UPWNhnBd29exJ4h7_lL_yfPucVWRF5CzQzP7knPPp-6N1CnQL0gacxUnBN5w0HP5tbaHH58uqxLDEke8qEXUvIpMd4fowivmD3mMBRDNb33tth6CKWATeEOLmg"

在firefox下

"endpoint": "https://updates.push.services.mozilla.com/wpush/v2/gAAAAABbY-_5v3kCWvAzOcu0YUiQ-DWJCpb3PfAMMwtqQmhxRSqZjbqORsQrON6P6iYi3A84l96repM6V5KkLuO9Yucb3Vjt27HtqYIUXQh0ELUeXpB-c_gN2H0qJ9szXvqYjuOW_MW-Q5lQTffgRRd5vjzumlYGWCh0fmKztmMNnM0EHi87hDA"

国内浏览器不支持web push,就算按照w3c标准实现了push service,说服浏览器厂商来用难度太大。chrome被墙,国内推送无望。所以只能给firefox推送消息(safari走的自己的协议,定制下也能推送;opera没有自己的push service,用的fcm),在国内大部分用户都是用360、qq、搜狗、2345的情况下,覆盖量实在是少的可怜。所以第三方web push,国内目前是看不到做的必要

通过上面的例子可以实现firefox上的推送
电脑上的截图:
PWA实战准备篇一_第3张图片

你可能感兴趣的:(JavaScript,pwa)