我个人对Service worker的理解
- 我个人觉得Service worker是一个多tab页面数据同步,以及缓存信息的中间件。它是浏览器(服务器)=>Service Worker(nginx)=>web应用程序(客户端),它可以拦截请求做相对应的处理,也可以和web应用相互通信(这一点很重要的,可以相互更改彼此的状态、相互传值)。
- 通过它实现离线缓存,当没有网的情况下也可以访问页面。
- 当我打开了多个tab页面,其中一个页面主动触发了数据更新,我可以通过service worker拦截。并主动通过发消息通知已经打开的tab页面进行数据状态同步。
- service worker是独立的线程它和web应用程序相互独立,当页面关闭时 service worker 会独立运行在后台,当它收到服务器的推送消息时可以显示通知。这其实就是web端的离线推送的一种方式。
1、注册Service Worker(随便一个地方注册就行)
useEffect(() => {
navigator.serviceWorker
.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered success: ', registration);
})
.catch(registrationError => {
console.log('Service Worker registration failed: ', registrationError);
});
}, [])
2、绑定worker发送信息给主线程的事件(当我们在service做了一些操作,需要页面去同步的时候)
useEffect(() => {
if ("serviceWorker" in navigator) {
let listener = (event:any) => {
console.log(event, "=====>>>> 服务端向Page发起通信");
};
navigator.serviceWorker.addEventListener("message", listener);
return () => {
navigator.serviceWorker.removeEventListener("message", listener);
};
}
}, []);
3、初始化IndexedDB
self.addEventListener('activate', function(event) {
console.log("activate", event, "====self", clients)
const databaseConcat = self.indexedDB.open("mysql", 1);
databaseConcat.onupgradeneeded = function (event) {
DB = event.target.result;
const objectStore = DB.createObjectStore("signalChat", {
keyPath: "id",
autoIncrement: true
});
objectStore.createIndex("link", "link", { unique: false });
objectStore.createIndex("sequenceId", "sequenceId", { unique: false });
objectStore.createIndex("messageType", "messageType", {
unique: false,
})
console.log(objectStore, "---")
objectStore.transaction.oncomplete = () => {
handleInsertDataToTable()
}
}
databaseConcat.onsuccess = (event) => {
DB = event.target.result;
}
});
4、拦截所有的Fetch请求。在这里我将所有的请求都拦截了,拦截之后会在Application下面的Cache storage下面看见。我拦截了所有的请求放在缓存中,当我第二次访问的时候就会从缓存中取值而不会从服务器。其实这样就实现了离线访问
self.addEventListener('fetch', function(event) {
event.respondWith(
(async () => {
const cache = await caches.open(staticName);
const cachedResponse = await cache.match(event.request)
console.log(cachedResponse, "cachedResponse")
if (cachedResponse) {
console.log(cachedResponse, "have match cache")
//有缓存
return cachedResponse
}
try {
const fetchResponse = await fetch(event.request);
const responseToCache = fetchResponse.clone();
cache.put(event.request, responseToCache);
return fetchResponse;
} catch (error) {
console.error('Fetching failed:', error);
throw error;
}
})(),
);
event.waitUntil(
(async () => {
if (!event.clientId) return;
console.log(event, "fetch request")
const client = await self.clients.get(event.clientId);
console.log(client," 客户端")
if (!client) return;
client.postMessage({
msg: "Hey I just got a fetch from you!",
url: event.request.url,
});
})(),
);
});
5、页面和service worker相互通信
self.addEventListener("message", (e) => {
console.log("页面主动发起通信, page => service worker", e)
})
<div onClick={() => {
navigator.serviceWorker.ready.then((registration) => {
registration?.active.postMessage(
{
message: "Test message sent immediately after creation",
type: "INSERT INTO"
}
);
});
}}
>
send message to service worker
</div>
完整代码如下:
const version = 1;
const staticName = `staticCache-${version}`;
let DB
self.addEventListener('install', function(event) {
console.log("install", event)
});
self.addEventListener('activate', function(event) {
console.log("activate", event, "====self", clients)
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== staticName) {
return caches.delete(cacheName);
}
})
);
})
);
const databaseConcat = self.indexedDB.open("mysql", 1);
databaseConcat.onupgradeneeded = function (event) {
DB = event.target.result;
const objectStore = DB.createObjectStore("signalChat", {
keyPath: "id",
autoIncrement: true
});
objectStore.createIndex("link", "link", { unique: false });
objectStore.createIndex("sequenceId", "sequenceId", { unique: false });
objectStore.createIndex("messageType", "messageType", {
unique: false,
})
console.log(objectStore, "---")
objectStore.transaction.oncomplete = () => {
handleInsertDataToTable()
}
}
databaseConcat.onsuccess = (event) => {
DB = event.target.result;
}
});
const handleInsertDataToTable = () => {
console.log(DB, "DB");
DB.transaction('signalChat', "readwrite")
.objectStore('signalChat')
.add({
sequenceId: 1,
link: "green Link",
messageType: "unread"
})
}
const handleGetTableOfRow = () => {
console.log(DB)
DB.transaction("signalChat", "readwrite")
.objectStore("signalChat")
.get(1)
.onsuccess = (event) => {
console.log(event)
}
}
self.addEventListener('idle', function(event) {
console.log("idle", event)
});
self.addEventListener('fetch', function(event) {
event.respondWith(
(async () => {
const cache = await caches.open(staticName);
const cachedResponse = await cache.match(event.request)
console.log(cachedResponse, "cachedResponse")
if (cachedResponse) {
console.log(cachedResponse, "have match cache")
//有缓存
return cachedResponse
}
try {
const fetchResponse = await fetch(event.request);
const responseToCache = fetchResponse.clone();
cache.put(event.request, responseToCache);
return fetchResponse;
} catch (error) {
console.error('Fetching failed:', error);
throw error;
}
})(),
);
event.waitUntil(
(async () => {
if (!event.clientId) return;
console.log(event, "fetch request")
const client = await self.clients.get(event.clientId);
console.log(client," 客户端")
if (!client) return;
client.postMessage({
msg: "Hey I just got a fetch from you!",
url: event.request.url,
});
})(),
);
});
self.addEventListener("message", (e) => {
console.log("页面主动发起通信, page => service worker", e)
})