mirror of
https://github.com/ae-utbm/sith.git
synced 2026-05-19 15:30:54 +00:00
feat: versionedLocalStorage
This commit is contained in:
@@ -1,13 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* For more detailed infos on how to use this file,
|
||||||
|
* check /docs/tutorial/front/localstorage.md,
|
||||||
|
* or https://ae-utbm.github.io/sith/tutorial/front/localstorage/
|
||||||
|
*/
|
||||||
|
|
||||||
// increment this number when a breaking change is made with localStorage
|
// increment this number when a breaking change is made with localStorage
|
||||||
const CURRENT_CACHE_VERSION = 1;
|
const CURRENT_CACHE_VERSION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove keys that are no longer used from localStorage
|
||||||
|
*/
|
||||||
export function cacheBuster() {
|
export function cacheBuster() {
|
||||||
const version = Number.parseInt(localStorage.getItem("version") ?? "0", 10);
|
const version = Number.parseInt(localStorage.getItem("version") ?? "0", 10);
|
||||||
if (version === CURRENT_CACHE_VERSION) {
|
if (version === CURRENT_CACHE_VERSION) {
|
||||||
// The cache schema is up-to-date. Nothing to do.
|
// The cache schema is up-to-date. Nothing to do.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage.removeItem("basket");
|
|
||||||
localStorage.removeItem("basket1");
|
localStorage.removeItem("basket1");
|
||||||
// remove all storage items which key is in the form
|
// remove all storage items which key is in the form
|
||||||
// `userXXXPictures` or `userXXXPicturesNumber`
|
// `userXXXPictures` or `userXXXPicturesNumber`
|
||||||
@@ -22,3 +30,43 @@ export function cacheBuster() {
|
|||||||
});
|
});
|
||||||
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VersionedStorageItem<T> {
|
||||||
|
version?: number;
|
||||||
|
val: T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const versionedLocalStorage = {
|
||||||
|
...localStorage,
|
||||||
|
/**
|
||||||
|
* set this item in localStorage, alongside its version.
|
||||||
|
*
|
||||||
|
* Note: this expects an object, not a JSON string, because the parsing
|
||||||
|
* into JSON needs to be done inside the function.
|
||||||
|
*/
|
||||||
|
setItem<T>(key: string, value: T, { version }: { version: number }) {
|
||||||
|
localStorage.setItem(key, JSON.stringify({ version: version, val: value }));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the item linked with the given key and version from localStorage.
|
||||||
|
*
|
||||||
|
* Note: if the given key exists in localStorage but doesn't satisfy
|
||||||
|
* the given version, it will be cleared from cache.
|
||||||
|
*
|
||||||
|
* @return the object if found and with the good version, else null;
|
||||||
|
*/
|
||||||
|
getItem<T>(key: string, { version }: { version: number }): T | null {
|
||||||
|
const stored = localStorage.getItem(key);
|
||||||
|
if (!stored) {
|
||||||
|
// this key doesn't exist, return null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const obj: VersionedStorageItem<T> = JSON.parse(stored);
|
||||||
|
if (obj.version !== version || obj.val === undefined) {
|
||||||
|
// The version is wrong, return null and remove this item from cache
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return obj.val;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -22,19 +22,9 @@ Pour résoudre le premier de ces deux problèmes, il y a un script permettant
|
|||||||
d'annuler une partie du cache.
|
d'annuler une partie du cache.
|
||||||
Ce dernier se trouve dans le fichier `core/static/bundled/core/cache.ts`.
|
Ce dernier se trouve dans le fichier `core/static/bundled/core/cache.ts`.
|
||||||
|
|
||||||
Vous devrez modifier ce cache chaque fois que vous effectuerez
|
Vous devrez modifier ce fichier chaque fois qu'un élément du localStorage
|
||||||
un changement de schéma, c'est-à-dire dans un des cas suivants :
|
cessera d'être utilisé.
|
||||||
|
Les modifications à apporter sont les suivantes :
|
||||||
- une des clefs du cache n'est plus utilisée
|
|
||||||
- la clef n'a pas changé, mais la manière dont les données attachées à cette clef
|
|
||||||
sont formées a été modifiée.
|
|
||||||
|
|
||||||
!!!Note
|
|
||||||
|
|
||||||
Si vous ne faites qu'ajouter des données, sans modifier ni supprimer
|
|
||||||
celles qui sont là, vous n'avez pas besoin d'invalider le cache.
|
|
||||||
|
|
||||||
Vous devez effectuer deux modifications dans ce fichier :
|
|
||||||
|
|
||||||
- incrémenter la version du cache
|
- incrémenter la version du cache
|
||||||
- ajouter une ligne permettant de retirer votre clef du cache
|
- ajouter une ligne permettant de retirer votre clef du cache
|
||||||
@@ -54,3 +44,32 @@ export function cacheBuster() {
|
|||||||
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Versionnage d'une clef
|
||||||
|
|
||||||
|
Dans le cas où une paire clef-valeur du localStorage subit un changement
|
||||||
|
dans son schéma de données, utilisez `versionedLocalStorage` :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { versionedLocalStorage } from "#core:core/cache";
|
||||||
|
|
||||||
|
const foo = () => {
|
||||||
|
let obj = versionedLocalStorage.getItem("<key>", { version: 1 });
|
||||||
|
if (obj === null) {
|
||||||
|
obj = fetchMyObject();
|
||||||
|
versionedLocalStorage.setItem("<key>", obj, { version: 1 })
|
||||||
|
}
|
||||||
|
// Do something with obj...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!Warning
|
||||||
|
|
||||||
|
Il existe une différence d'usage entre `localStorage` et `versionedLocalStorage` :
|
||||||
|
les valeurs données à `localStorage` doivent être des strings (généralement
|
||||||
|
obtenus avec `JSON.stringify`), tandis que `versionedLocalStorage` utilise
|
||||||
|
directement des objets JS.
|
||||||
|
|
||||||
|
Cette différence résulte du fait que `versionedLocalStorage` doit légèrement
|
||||||
|
modifier les données pour y inclure la version, et gérer en interne
|
||||||
|
la conversion en JSON.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export {};
|
import { versionedLocalStorage } from "#core:core/cache";
|
||||||
|
|
||||||
interface BasketItem {
|
interface BasketItem {
|
||||||
priceId: number;
|
priceId: number;
|
||||||
@@ -8,6 +8,7 @@ interface BasketItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BASKET_CACHE_KEY = "basket";
|
const BASKET_CACHE_KEY = "basket";
|
||||||
|
const BASKET_CACHE_VERSION = 1;
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
Alpine.data("basket", (lastPurchaseTime?: number) => ({
|
Alpine.data("basket", (lastPurchaseTime?: number) => ({
|
||||||
@@ -33,18 +34,16 @@ document.addEventListener("alpine:init", () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadBasket(): BasketItem[] {
|
loadBasket(): BasketItem[] {
|
||||||
if (localStorage.getItem(BASKET_CACHE_KEY) === null) {
|
const cached = versionedLocalStorage.getItem<BasketItem[]>(BASKET_CACHE_KEY, {
|
||||||
return [];
|
version: BASKET_CACHE_VERSION,
|
||||||
}
|
});
|
||||||
try {
|
return cached ?? [];
|
||||||
return JSON.parse(localStorage.getItem(BASKET_CACHE_KEY));
|
|
||||||
} catch (_err) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveBasket() {
|
saveBasket() {
|
||||||
localStorage.setItem(BASKET_CACHE_KEY, JSON.stringify(this.basket));
|
versionedLocalStorage.setItem(BASKET_CACHE_KEY, this.basket, {
|
||||||
|
version: BASKET_CACHE_VERSION,
|
||||||
|
});
|
||||||
localStorage.setItem("basketTimestamp", Date.now().toString());
|
localStorage.setItem("basketTimestamp", Date.now().toString());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
// cache only the pictures of the last 4 visited profiles
|
// cache only the pictures of the last 4 visited profiles
|
||||||
sessionStorage.setItem(storageKey, JSON.stringify(cacheContent.slice(-4)));
|
sessionStorage.setItem(storageKey, JSON.stringify(cacheContent.slice(-4)));
|
||||||
} catch {
|
} catch {
|
||||||
// an exception is raised if the localstorage is entirely filled.
|
// an exception is raised if the storage is entirely filled.
|
||||||
// To be as safe as possible, delete the cached pictures.
|
// To be as safe as possible, delete the cached pictures.
|
||||||
// A cache hit is not worth the page breaking.
|
// A cache hit is not worth the page breaking.
|
||||||
sessionStorage.removeItem(storageKey);
|
sessionStorage.removeItem(storageKey);
|
||||||
|
|||||||
Reference in New Issue
Block a user