Skip to content

Instantly share code, notes, and snippets.

@sayanriju
Last active January 28, 2021 09:25
Show Gist options
  • Save sayanriju/5ae4905593d9bf1f0682cc535d8ecd1d to your computer and use it in GitHub Desktop.
Save sayanriju/5ae4905593d9bf1f0682cc535d8ecd1d to your computer and use it in GitHub Desktop.
Service Worker for Caching Images using IndexedDB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Service worker demo</title>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script type="text/javascript">
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js', { scope: '/' })
} else {
console.warn("==> Browser doesn't support Service Workers!!")
}
</script>
</head>
<body>
<img src="https://as1.ftcdn.net/jpg/00/92/53/56/500_F_92535664_IvFsQeHjBzfE6sD4VHdO8u5OHUSc6yHF.jpg?cacheId=1234" alt="Image 1" />
<img src="https://thumbs.dreamstime.com/b/sample-stamp-sample-stamp-sign-icon-editable-vector-illustration-isolated-white-background-123951468.jpg?cacheId=4568" alt="Image 2" />
</body>
</html>
/* eslint-disable no-undef */
importScripts("/dexie.min.js")
const CACHE_ID_FIELD = "cacheId"
const CACHE_COUNT_LIMIT = 49
// N.B.: The below constants imply a max value of allotted file storage for caching to be (49 + 1) * 10 mb ~=500mb
const CACHE_MIN_FILE_SIZE = 0 // in bytes
const CACHE_MAX_FILE_SIZE = 10000000 // ~10mb in bytes
const db = new Dexie("images")
db.version(1).stores({
cache: "id,ts" // the indices
})
function getUrlParameter(name, url) { // ref: https://davidwalsh.name/query-string-javascript
// eslint-disable-next-line no-param-reassign
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]")
const regex = new RegExp(`[\\?&]${name}=([^&#]*)`)
const results = regex.exec(url)
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "))
}
function checkCacheable(fetchRequest) {
if (fetchRequest.method !== "GET" || fetchRequest.destination !== "image") return { isCacheable: false } // only images
const id = getUrlParameter(CACHE_ID_FIELD, fetchRequest.url)
if (id === undefined || id === null) return { isCacheable: false }
return { isCacheable: true, id }
}
this.addEventListener("install", (event) => {
console.log("sw installed....")
})
this.addEventListener("fetch", async (fetchEvent) => {
fetchEvent.respondWith(
(async (event) => { // an async IIFE
const { isCacheable, id } = checkCacheable(event.request)
if (isCacheable === true) {
const inCacheCnt = await db.cache.where("id").equals(id).count()
if (inCacheCnt === 0) { // cache MISS
const response = await fetch(event.request.url)
const blob = await response.clone().blob()
if (blob.size >= CACHE_MAX_FILE_SIZE || blob.size <= CACHE_MIN_FILE_SIZE) return response // don't cache too big or too small files
await db.cache.put({ id, blob, ts: Date.now() })
// LRU eviction, if required (basically, when allotted file storage is near depletion):
const allCnt = await db.cache.count()
if (allCnt >= CACHE_COUNT_LIMIT) {
const oldest = await db.cache.orderBy("ts").first()
await db.cache.where("id").equals(oldest.id).delete()
}
return response
}
// cache HIT:
const cached = await db.cache.get(id)
await db.cache.update(id, { ts: Date.now() })
return new Response(cached.blob, { status: 200, statusText: "Cached in IndexedDB" })
}
return fetch(event.request) // not cacheable url. Fallback to network.
})(fetchEvent)
)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment