Service Worker中初始化indexedDB、以及实现离线访问。

263 阅读3分钟

我个人对Service worker的理解

  1. 我个人觉得Service worker是一个多tab页面数据同步,以及缓存信息的中间件。它是浏览器(服务器)=>Service Worker(nginx)=>web应用程序(客户端),它可以拦截请求做相对应的处理,也可以和web应用相互通信(这一点很重要的,可以相互更改彼此的状态、相互传值)。
  2. 通过它实现离线缓存,当没有网的情况下也可以访问页面。
  3. 当我打开了多个tab页面,其中一个页面主动触发了数据更新,我可以通过service worker拦截。并主动通过发消息通知已经打开的tab页面进行数据状态同步。
  4. 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,
      });
    })(),
  );
});

截屏2024-03-18 16.59.12.png

截屏2024-03-18 16.58.52.png

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)
 })