Service Worker 是一种在浏览器后台运行的 JavaScript 线程,它能够独立于网页和用户交互进行操作。它的主要作用是为 Web 应用程序提供离线功能、推送通知、后台数据同步等能力
他和worker的区别在于:
- Worker 主要用于后台执行计算任务,避免阻塞主线程;而 Service Worker 主要用于离线功能、缓存管理、推送通知等与 Web 应用的网络交互相关的任务。
- Worker 的生命周期短暂,通常用于即时任务;Service Worker 是长期驻留在后台的,能够在应用关闭后继续运行。
主要功能:
-
离线支持:
- Service Worker 可以缓存网络请求和响应,从而实现离线访问。即使用户没有网络连接,应用仍然可以通过缓存的数据来显示内容。
-
拦截和处理网络请求:
- Service Worker 可以拦截浏览器发出的 HTTP 请求,修改请求或响应数据,甚至返回缓存的资源。这样可以减少请求时间、提高应用的性能。
-
推送通知:
- Service Worker 允许 Web 应用通过浏览器发送推送通知,即使应用处于后台或完全关闭状态。这在移动设备上尤其有用,用来提醒用户有新消息或更新。
-
后台同步:
- Service Worker 支持后台同步(Background Sync),可以在网络恢复时自动同步数据,比如自动上传用户离线时产生的数据。
-
精细化的缓存管理:
- Service Worker 提供了对缓存的精细化控制,你可以选择性地缓存某些资源或根据条件删除缓存的内容。
app.vue
<script setup lang="ts">
import { onMounted ,ref} from 'vue';
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.register('sw.js').then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.error('Service Worker registration failed:', error);
});
} else {
console.log('浏览器不支持 PushManager 或 ServiceWorker');
}
//双向通信
const provideMessage = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(registration) {
return registration.controller.postMessage('Hello, Service Worker!');
}).then(function(registration) {
console.log('Background sync registered:', registration);
}).catch(function(error) {
console.log('Background sync registration failed:', error);
});
}
// 确保 Service Worker 已经激活并控制页面
if (navigator.serviceWorker.controller) {
// 向 Service Worker 发送消息
navigator.serviceWorker.controller.postMessage('Hello, Service Worker!');
}
}
//后台同步
const submitFun = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(registration) {
return registration.sync.register('mySyncTag');
}).then(function(registration) {
console.log('Background sync registered:', registration);
}).catch(function(error) {
console.log('Background sync registration failed:', error);
});
}
}
//消息推送
function requestPermission() {
Notification.requestPermission().then(permission => {
if (permission === "granted") {
console.log("用户已授予通知权限");
} else {
console.log("用户拒绝了通知权限");
}
});
}
onMounted(()=>{
requestPermission();
})
const subscriptionVal = ref<any>();
const tsFun = () =>{
if ('serviceWorker' in navigator) {
//先取消上一次次的
getSubscription();
navigator.serviceWorker.ready.then(function(registration) {
// 使用 VAPID 公钥(从服务器获取)进行订阅
// const vapidKeys = webpush.generateVAPIDKeys();
// const vapidPublicKey = 'BCskj78qyd73k9k3df7kbskd9sks3kks9n0a9c4f7k'; // 从生成的公钥填入
const vapidPublicKey = 'BNoZ0H7iJ5YGDLq_2vKNVMIJhGfhVgWzWk2zYVk90BnCJlM-MMtww1eL-VzbzSxBb4xLoYTvu_jKbHWTC9yxyLs'; // 从生成的公钥填入
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey//urlB64ToUint8Array(vapidPublicKey)
}).then(function(subscription) {
subscriptionVal.value = subscription;
console.log('订阅成功:', subscription);
}).catch(function(error) {
console.error('订阅时出错:', error);
});
})
}
}
const getSubscription = () =>{
// 获取当前的订阅
navigator.serviceWorker.getRegistration().then(function(registration) {
return registration.pushManager.getSubscription();
}).then(function(subscription) {
if (subscription) {
// 取消订阅
return subscription.unsubscribe();
}
}).catch(function(error) {
console.error('取消订阅时出错:', error);
});
}
const startNotification = async () => {
await fetch('/subscribe', {
method: 'POST',
body: JSON.stringify(subscriptionVal.value),
headers: {
'Content-Type': 'application/json',
},
});
}
</script>
<template>
<img alt="Vue logo" src="./assets/avatar.png" /><br/>
<button @click="provideMessage">信息双向传递</button>
<button @click="submitFun">点我开始后台同步</button>
<button @click="tsFun">点我开始订阅消息推送</button>
<button @click="startNotification" :disabled='!subscriptionVal'>点我开始消息推送</button>
</template>
sw.js
const CACHE_NAME = "service-worker-demo";
const urlsToCache = ["/",'/src/assets/avatar.png'];
self.addEventListener("install", event => {
self.skipWaiting(); // 激活等待中的 Service Worker
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log("[Service Worker]", urlsToCache);
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener("activate", event => {
// 不在白名单的`CACHE_NAME`就清理
const cacheWhitelist = ["service-worker-demo"];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
// 查看一下缓存
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.keys().then(res => console.log(res)))
);
event.waitUntil(self.clients.claim()); // 控制当前页面的客户端
});
// service-worker.js
self.addEventListener('message', event => {
console.log('Received message from main thread:', event.data);
// 回复主线程
event.source.postMessage('Hello from Service Worker!');
});
//后台推送
self.addEventListener('sync', function(event) {
if (event.tag === 'mySyncTag') {
event.waitUntil(syncData());
}
});
async function syncData(){
console.log("执行后台任务开始=====>",111)
}
//消息推送
self.addEventListener('push', function(event) {
var title = '推送通知';
const options = {
body: event.data ? event.data.text() : '没有通知内容',
icon: 'images/icon.png', // 你可以提供一个图标
badge: 'images/badge.png', // 你可以提供一个徽章图标
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
clients.openWindow('https://www.example.com')
);
});
self.addEventListener("fetch", event => {
const url = new URL(event.request.url);
if (url.origin === location.origin && urlsToCache.indexOf(url.pathname) > -1) {
event.respondWith(
caches.match(event.request).then(resp => {
if (resp) {
console.log("fetch ", event.request.url, "有缓存,从缓存中取");
return resp;
} else {
console.log("fetch ", event.request.url, "没有缓存,网络获取");
return fetch(event.request);
}
})
);
}
});
server.js 消息发送
const webPush = require('web-push');
// 引入 express
const express = require('express');
// 创建应用对象
const app = express();
// 使用从前端获取的订阅对象
const subscription = {
"endpoint": "https://fcm.googleapis.com/fcm/send/fcm2EcW8mNE:APA91bHRNP_8z-Zm-3WWZ1v9Hx1lbF8wtQ7cELlLWGmRJLdH2fBguYhxquO0SGFcWk7PTXUb7admJ9PjNxBOXbLzVQDEqPZLcQkNXPI2IlazUvP2zE-rV5i17hpE9aa43qtoDLaBYJ4W",
"expirationTime": null,
"keys": {
"p256dh": "BGbWsBR0q6idsgc9VxRL9yvstQdYTEf-8MpBpUU9h1mdncu9iYTcJx6XTtfOIOG3jFBVpj30--K3ftQU2Nk5fSE",
"auth": "lAtncP-QOG8nlIOXdtvYPA"
}
}
// 使用你的 VAPID 公钥和私钥生成授权
const vapidKeys = webPush.generateVAPIDKeys();
webPush.setVapidDetails(
'mailto:shili@genomics.cn',
'BDRMFE1zMf1S_uMgh6zC6nnv3tOFOUl9pZoxgy4VA3LHzHcsCQgp5N2FlwfgEJfOEjk_B_oILMGeZVsVo1NSbPA',
'0YPvfGShlE3UftCffO9sTSUVXpykXXxJkJvg_i4snzk'
// vapidKeys.publicKey,
// vapidKeys.privateKey
);
// 创建路由
app.get('/subscribe', (req, res) => {
webPush.sendNotification(subscription, JSON.stringify({ title: '测试通知', message: '这是一个测试通知' }))
.then(response => {
console.log('推送通知发送成功:', response);
})
.catch(err => {
console.error('推送通知发送失败:', err);
});
res.end('hello express');
})
// 监听端口
app.listen(30000, () => {
console.log('服务已启动,端口 30000 正在监听中...');
})
实现效果
功能:双向传递,离线缓存,后台同步均在控制台体现 Service Worker 是一种在浏览器后台运行的 JavaScript 线程,它能够独立于网页和用户交互进行操作。它的主要作用是为 Web 应用程序提供离线功能、推送通知、后台数据同步等能力
他和worker的区别在于:
- Worker 主要用于后台执行计算任务,避免阻塞主线程;而 Service Worker 主要用于离线功能、缓存管理、推送通知等与 Web 应用的网络交互相关的任务。
- Worker 的生命周期短暂,通常用于即时任务;Service Worker 是长期驻留在后台的,能够在应用关闭后继续运行。
主要功能:
-
离线支持:
- Service Worker 可以缓存网络请求和响应,从而实现离线访问。即使用户没有网络连接,应用仍然可以通过缓存的数据来显示内容。
-
拦截和处理网络请求:
- Service Worker 可以拦截浏览器发出的 HTTP 请求,修改请求或响应数据,甚至返回缓存的资源。这样可以减少请求时间、提高应用的性能。
-
推送通知:
- Service Worker 允许 Web 应用通过浏览器发送推送通知,即使应用处于后台或完全关闭状态。这在移动设备上尤其有用,用来提醒用户有新消息或更新。
-
后台同步:
- Service Worker 支持后台同步(Background Sync),可以在网络恢复时自动同步数据,比如自动上传用户离线时产生的数据。
-
精细化的缓存管理:
- Service Worker 提供了对缓存的精细化控制,你可以选择性地缓存某些资源或根据条件删除缓存的内容。
app.vue
<script setup lang="ts">
import { onMounted ,ref} from 'vue';
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.register('sw.js').then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.error('Service Worker registration failed:', error);
});
} else {
console.log('浏览器不支持 PushManager 或 ServiceWorker');
}
//双向通信
const provideMessage = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(registration) {
return registration.controller.postMessage('Hello, Service Worker!');
}).then(function(registration) {
console.log('Background sync registered:', registration);
}).catch(function(error) {
console.log('Background sync registration failed:', error);
});
}
// 确保 Service Worker 已经激活并控制页面
if (navigator.serviceWorker.controller) {
// 向 Service Worker 发送消息
navigator.serviceWorker.controller.postMessage('Hello, Service Worker!');
}
}
//后台同步
const submitFun = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(registration) {
return registration.sync.register('mySyncTag');
}).then(function(registration) {
console.log('Background sync registered:', registration);
}).catch(function(error) {
console.log('Background sync registration failed:', error);
});
}
}
//消息推送
function requestPermission() {
Notification.requestPermission().then(permission => {
if (permission === "granted") {
console.log("用户已授予通知权限");
} else {
console.log("用户拒绝了通知权限");
}
});
}
onMounted(()=>{
requestPermission();
})
const subscriptionVal = ref<any>();
const tsFun = () =>{
if ('serviceWorker' in navigator) {
//先取消上一次次的
getSubscription();
navigator.serviceWorker.ready.then(function(registration) {
// 使用 VAPID 公钥(从服务器获取)进行订阅
// const vapidKeys = webpush.generateVAPIDKeys();
// const vapidPublicKey = 'BCskj78qyd73k9k3df7kbskd9sks3kks9n0a9c4f7k'; // 从生成的公钥填入
const vapidPublicKey = 'BNoZ0H7iJ5YGDLq_2vKNVMIJhGfhVgWzWk2zYVk90BnCJlM-MMtww1eL-VzbzSxBb4xLoYTvu_jKbHWTC9yxyLs'; // 从生成的公钥填入
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey//urlB64ToUint8Array(vapidPublicKey)
}).then(function(subscription) {
subscriptionVal.value = subscription;
console.log('订阅成功:', subscription);
}).catch(function(error) {
console.error('订阅时出错:', error);
});
})
}
}
const getSubscription = () =>{
// 获取当前的订阅
navigator.serviceWorker.getRegistration().then(function(registration) {
return registration.pushManager.getSubscription();
}).then(function(subscription) {
if (subscription) {
// 取消订阅
return subscription.unsubscribe();
}
}).catch(function(error) {
console.error('取消订阅时出错:', error);
});
}
const startNotification = async () => {
await fetch('/subscribe', {
method: 'POST',
body: JSON.stringify(subscriptionVal.value),
headers: {
'Content-Type': 'application/json',
},
});
}
</script>
<template>
<img alt="Vue logo" src="./assets/avatar.png" /><br/>
<button @click="provideMessage">信息双向传递</button>
<button @click="submitFun">点我开始后台同步</button>
<button @click="tsFun">点我开始订阅消息推送</button>
<button @click="startNotification" :disabled='!subscriptionVal'>点我开始消息推送</button>
</template>
sw.js
const CACHE_NAME = "service-worker-demo";
const urlsToCache = ["/",'/src/assets/avatar.png'];
self.addEventListener("install", event => {
self.skipWaiting(); // 激活等待中的 Service Worker
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log("[Service Worker]", urlsToCache);
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener("activate", event => {
// 不在白名单的`CACHE_NAME`就清理
const cacheWhitelist = ["service-worker-demo"];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
// 查看一下缓存
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.keys().then(res => console.log(res)))
);
event.waitUntil(self.clients.claim()); // 控制当前页面的客户端
});
// service-worker.js
self.addEventListener('message', event => {
console.log('Received message from main thread:', event.data);
// 回复主线程
event.source.postMessage('Hello from Service Worker!');
});
//后台推送
self.addEventListener('sync', function(event) {
if (event.tag === 'mySyncTag') {
event.waitUntil(syncData());
}
});
async function syncData(){
console.log("执行后台任务开始=====>",111)
}
//消息推送
self.addEventListener('push', function(event) {
var title = '推送通知';
const options = {
body: event.data ? event.data.text() : '没有通知内容',
icon: 'images/icon.png', // 你可以提供一个图标
badge: 'images/badge.png', // 你可以提供一个徽章图标
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
clients.openWindow('https://www.example.com')
);
});
self.addEventListener("fetch", event => {
const url = new URL(event.request.url);
if (url.origin === location.origin && urlsToCache.indexOf(url.pathname) > -1) {
event.respondWith(
caches.match(event.request).then(resp => {
if (resp) {
console.log("fetch ", event.request.url, "有缓存,从缓存中取");
return resp;
} else {
console.log("fetch ", event.request.url, "没有缓存,网络获取");
return fetch(event.request);
}
})
);
}
});
server.js 消息发送
const webPush = require('web-push');
// 引入 express
const express = require('express');
// 创建应用对象
const app = express();
// 使用从前端获取的订阅对象
const subscription = {
"endpoint": "https://fcm.googleapis.com/fcm/send/fcm2EcW8mNE:APA91bHRNP_8z-Zm-3WWZ1v9Hx1lbF8wtQ7cELlLWGmRJLdH2fBguYhxquO0SGFcWk7PTXUb7admJ9PjNxBOXbLzVQDEqPZLcQkNXPI2IlazUvP2zE-rV5i17hpE9aa43qtoDLaBYJ4W",
"expirationTime": null,
"keys": {
"p256dh": "BGbWsBR0q6idsgc9VxRL9yvstQdYTEf-8MpBpUU9h1mdncu9iYTcJx6XTtfOIOG3jFBVpj30--K3ftQU2Nk5fSE",
"auth": "lAtncP-QOG8nlIOXdtvYPA"
}
}
// 使用你的 VAPID 公钥和私钥生成授权
const vapidKeys = webPush.generateVAPIDKeys();
webPush.setVapidDetails(
'mailto:shili@genomics.cn',
'BDRMFE1zMf1S_uMgh6zC6nnv3tOFOUl9pZoxgy4VA3LHzHcsCQgp5N2FlwfgEJfOEjk_B_oILMGeZVsVo1NSbPA',
'0YPvfGShlE3UftCffO9sTSUVXpykXXxJkJvg_i4snzk'
// vapidKeys.publicKey,
// vapidKeys.privateKey
);
// 创建路由
app.get('/subscribe', (req, res) => {
webPush.sendNotification(subscription, JSON.stringify({ title: '测试通知', message: '这是一个测试通知' }))
.then(response => {
console.log('推送通知发送成功:', response);
})
.catch(err => {
console.error('推送通知发送失败:', err);
});
res.end('hello express');
})
// 监听端口
app.listen(30000, () => {
console.log('服务已启动,端口 30000 正在监听中...');
})
实现效果
功能:双向传递,离线缓存,后台同步均在控制台体现