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.国内使用率不高
- 等等