Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LocalStorage Improvements #297

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 61 additions & 31 deletions src/cache/adapters/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,106 @@
import SWRVCache, { ICacheItem } from '..'
import { IKey } from '../../types'

/**
* LocalStorage cache adapter for swrv data cache.
* https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
*/

interface IStore { [name: string]: ICacheItem<any> }

export default class LocalStorageCache extends SWRVCache<any> {
private STORAGE_KEY

constructor (key = 'swrv', ttl = 0) {
constructor (key: string = 'swrv', ttl: number = 0) {
super(ttl)
this.STORAGE_KEY = key
this.rescheduleExpiry()
}

private encode (storage) { return JSON.stringify(storage) }
private decode (storage) { return JSON.parse(storage) }

get (k) {
getLocalStorageContents(): IStore {
const item = localStorage.getItem(this.STORAGE_KEY)
if (item) {
const _key = this.serializeKey(k)
const itemParsed: ICacheItem<any> = JSON.parse(item)[_key]
if (item) return this.decode(item)
else return {}
}

setLocalStorageContents(value: IStore) {
const encoded = this.encode(value)
try {
localStorage.setItem(this.STORAGE_KEY, encoded)
}
catch {
// If there's been an error, it's probably because localStorage is full or disabled (e.g. some private browsing)
// eslint-disable-next-line no-console
console.log("Unable to store SWRV data in LocalStorage, maybe full?")

if (itemParsed?.expiresAt === null) {
itemParsed.expiresAt = Infinity // localStorage sets Infinity to 'null'
// Try to clear SWRV's data.
try {
// Clear the cache for next time.
localStorage.removeItem(this.STORAGE_KEY)
}
catch {
// Don't try to recover, fail silently.
// eslint-disable-next-line no-console
console.log("Unable to clear SWRV data in LocalStorage.")
}
}
}

return itemParsed
get (k: IKey) {
const store = this.getLocalStorageContents()
const _key = this.serializeKey(k)
const itemParsed: ICacheItem<any> = store[_key]

if (itemParsed?.expiresAt === null) {
itemParsed.expiresAt = Infinity // localStorage sets Infinity to 'null'
}

return undefined
return itemParsed
}

set (k: string, v: any, ttl: number) {
let payload = {}
const _key = this.serializeKey(k)
const timeToLive = ttl || this.ttl
const storage = localStorage.getItem(this.STORAGE_KEY)
const store = this.getLocalStorageContents()
const now = Date.now()
const item = {
const item: ICacheItem<any> = {
data: v,
createdAt: now,
expiresAt: timeToLive ? now + timeToLive : Infinity
}

if (storage) {
payload = this.decode(storage)
payload[_key] = item
} else {
payload = { [_key]: item }
}
store[_key] = item

this.dispatchExpire(timeToLive, item, _key)
localStorage.setItem(this.STORAGE_KEY, this.encode(payload))
this.setLocalStorageContents(store)
}

dispatchExpire (ttl, item, serializedKey) {
dispatchExpire (ttl: number, item: ICacheItem<any>, serializedKey: string) {
ttl && setTimeout(() => {
const current = Date.now()
const hasExpired = current >= item.expiresAt
if (hasExpired) this.delete(serializedKey)
}, ttl)
}

delete (serializedKey: string) {
const storage = localStorage.getItem(this.STORAGE_KEY)
let payload = {}

if (storage) {
payload = this.decode(storage)
delete payload[serializedKey]
rescheduleExpiry() {
const store = this.getLocalStorageContents()
for (const [serializedKey, item] of Object.entries(store)) {
if (item.expiresAt !== Infinity) {
let ttl = item.expiresAt - Date.now()
if (ttl < 0) ttl = 1;
this.dispatchExpire(ttl, item, serializedKey)
}
}
}

localStorage.setItem(this.STORAGE_KEY, this.encode(payload))
delete (serializedKey: string) {
const store = this.getLocalStorageContents()
delete store[serializedKey]
this.setLocalStorageContents(store)
}

private encode (storage: object) { return JSON.stringify(storage) }

private decode (storage: string) { return JSON.parse(storage) }
}