2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 23:00:56 +00:00

Invalidate browser cache based on lastModified property from icon set

This commit is contained in:
Vjacheslav Trushkin 2022-06-29 16:11:16 +03:00
parent d2956c379e
commit d0d491b066
5 changed files with 297 additions and 38 deletions

View File

@ -5,7 +5,7 @@ import {
setBrowserStorageStatus,
} from './data';
import { iterateBrowserStorage } from './foreach';
import type { BrowserStorageType } from './types';
import type { BrowserStorageType, IconStorageWithCache } from './types';
/**
* Load icons from cache
@ -24,12 +24,22 @@ export function initBrowserStorage() {
const provider = item.provider;
const prefix = iconSet.prefix;
const storage = getStorage(provider, prefix);
const storage = getStorage(
provider,
prefix
) as IconStorageWithCache;
if (!addIconSet(storage, iconSet).length) {
// No valid icons
return false;
}
// Store lastModified, -1 if not set to get truthy value
// Smallest of values is stored to fix cache
const lastModified = iconSet.lastModified || -1;
storage.lastModifiedCached = storage.lastModifiedCached
? Math.min(storage.lastModifiedCached, lastModified)
: lastModified;
return true;
});
}

View File

@ -10,6 +10,7 @@ import {
browserStorageEmptyItems,
browserStorageStatus,
} from './data';
import { iterateBrowserStorage } from './foreach';
import { getBrowserStorage } from './global';
import { initBrowserStorage } from './index';
import { setStoredItem } from './item';
@ -17,8 +18,48 @@ import type {
BrowserStorageInstance,
BrowserStorageItem,
BrowserStorageType,
IconStorageWithCache,
} from './types';
/**
* Update lastModified in storage
*
* Returns false if item should not be added to storage because lastModified is too low
*/
export function updateLastModified(
storage: IconStorageWithCache,
lastModified: number
): boolean {
const lastValue = storage.lastModifiedCached;
if (
// Matches or newer
lastValue &&
lastValue >= lastModified
) {
// Nothing to update
return lastValue === lastModified;
}
// Update value
storage.lastModifiedCached = lastModified;
if (lastValue) {
// Old value was set: possibly items are in browser cache
for (const key in browserStorageConfig) {
iterateBrowserStorage(key as BrowserStorageType, (item) => {
const iconSet = item.data;
// Delete items with same provider and prefix
return (
item.provider !== storage.provider ||
iconSet.prefix !== storage.prefix ||
iconSet.lastModified === lastModified
);
});
}
}
return true;
}
/**
* Function to cache icons
*/
@ -61,6 +102,11 @@ export function storeInBrowserStorage(storage: IconStorage, data: IconifyJSON) {
);
}
// Update lastModified
if (data.lastModified && !updateLastModified(storage, data.lastModified)) {
return;
}
// Do not store empty sets
if (!Object.keys(data.icons).length) {
return;

View File

@ -19,3 +19,12 @@ export interface BrowserStorageItem {
provider: string;
data: IconifyJSON;
}
/**
* Add custom stuff to storage
*/
export interface IconStorageWithCache extends IconStorage {
// Last modified from browser cache, minimum of available values
// If not set, but icons are cached, value is -1
lastModifiedCached?: number;
}

View File

@ -1,5 +1,8 @@
import type { IconifyJSON } from '@iconify/types';
import type { BrowserStorageItem } from '../../lib/browser-storage/types';
import type {
BrowserStorageItem,
IconStorageWithCache,
} from '../../lib/browser-storage/types';
import { initBrowserStorage } from '../../lib/browser-storage';
import {
browserStorageConfig,
@ -17,6 +20,7 @@ import {
browserStorageHour,
browserStorageCacheExpiration,
} from '../../lib/browser-storage/config';
import { getStoredItem } from '../../lib/browser-storage/item';
describe('Testing loading from localStorage', () => {
const provider = '';
@ -24,6 +28,7 @@ describe('Testing loading from localStorage', () => {
it('Valid icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -59,14 +64,15 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Load localStorage
initBrowserStorage();
// Icon should exist now
expect(iconExists(icons, 'foo')).toBe(true);
// Icon should exist now and lastModified should be set
expect(iconExists(storage, 'foo')).toBe(true);
expect(storage.lastModifiedCached).toBe(-1);
// Check data
expect(browserStorageConfig).toEqual({
@ -83,6 +89,9 @@ describe('Testing loading from localStorage', () => {
const provider = nextPrefix();
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const defaultStorage = getStorage('', prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -93,6 +102,7 @@ describe('Testing loading from localStorage', () => {
provider,
data: {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
@ -101,6 +111,7 @@ describe('Testing loading from localStorage', () => {
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
@ -108,19 +119,19 @@ describe('Testing loading from localStorage', () => {
});
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Check default provider
const icons2 = getStorage('', prefix);
expect(iconExists(icons2, 'foo')).toBe(false);
expect(iconExists(defaultStorage, 'foo')).toBe(false);
expect(defaultStorage.lastModifiedCached).toBeUndefined();
// Load localStorage
initBrowserStorage();
// Icon should exist now
expect(iconExists(icons, 'foo')).toBe(true);
expect(iconExists(icons2, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(true);
expect(iconExists(defaultStorage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
@ -131,11 +142,16 @@ describe('Testing loading from localStorage', () => {
local: new Set(),
session: new Set(),
});
expect(storage.lastModifiedCached).toBe(lastModified);
expect(defaultStorage.lastModifiedCached).toBeUndefined();
});
it('Expired icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -150,6 +166,7 @@ describe('Testing loading from localStorage', () => {
provider,
data: {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
@ -158,6 +175,7 @@ describe('Testing loading from localStorage', () => {
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
@ -171,14 +189,14 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Check data
expect(browserStorageConfig).toEqual({
@ -200,6 +218,7 @@ describe('Testing loading from localStorage', () => {
it('Bad icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -220,6 +239,7 @@ describe('Testing loading from localStorage', () => {
},
})
);
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
@ -233,14 +253,13 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
@ -257,11 +276,15 @@ describe('Testing loading from localStorage', () => {
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// Item should have been deleted
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeFalsy();
});
it('Wrong counter', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -293,14 +316,13 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
@ -316,6 +338,7 @@ describe('Testing loading from localStorage', () => {
it('Missing entries at the end', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -347,14 +370,13 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).toBe(false);
expect(iconExists(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should exist now
expect(iconExists(icons, 'foo')).toBe(true);
expect(iconExists(storage, 'foo')).toBe(true);
// Check data
expect(browserStorageConfig).toEqual({
@ -376,6 +398,7 @@ describe('Testing loading from localStorage', () => {
it('Missing entries', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add two icon sets
cache.setItem(browserCacheVersionKey, browserCacheVersion);
@ -422,16 +445,15 @@ describe('Testing loading from localStorage', () => {
);
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo1')).toBe(false);
expect(iconExists(icons, 'foo4')).toBe(false);
expect(iconExists(storage, 'foo1')).toBe(false);
expect(iconExists(storage, 'foo4')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icons should exist now
expect(iconExists(icons, 'foo1')).toBe(true);
expect(iconExists(icons, 'foo4')).toBe(true);
expect(iconExists(storage, 'foo1')).toBe(true);
expect(iconExists(storage, 'foo4')).toBe(true);
// Check data
expect(browserStorageConfig).toEqual({
@ -454,6 +476,7 @@ describe('Testing loading from localStorage', () => {
const prefix = nextPrefix();
const cache1 = createCache();
const cache2 = createCache();
const storage = getStorage(provider, prefix);
// Add few icon sets
cache1.setItem(browserCacheVersionKey, browserCacheVersion);
@ -507,9 +530,8 @@ describe('Testing loading from localStorage', () => {
});
// Check icon storage
const iconsStorage = getStorage(provider, prefix);
for (let i = 0; i < 6; i++) {
expect(iconExists(iconsStorage, 'foo' + i.toString())).toBe(false);
expect(iconExists(storage, 'foo' + i.toString())).toBe(false);
}
// Load localStorage
@ -517,9 +539,7 @@ describe('Testing loading from localStorage', () => {
// Icons should exist now, except for number 4
for (let i = 0; i < 6; i++) {
expect(iconExists(iconsStorage, 'foo' + i.toString())).toBe(
i !== 4
);
expect(iconExists(storage, 'foo' + i.toString())).toBe(i !== 4);
}
// Check data

View File

@ -1,5 +1,8 @@
import type { IconifyJSON } from '@iconify/types';
import type { BrowserStorageItem } from '../../lib/browser-storage/types';
import type {
BrowserStorageItem,
IconStorageWithCache,
} from '../../lib/browser-storage/types';
import { storeInBrowserStorage } from '../../lib/browser-storage/store';
import { initBrowserStorage } from '../../lib/browser-storage';
import {
@ -25,7 +28,7 @@ describe('Testing saving to localStorage', () => {
it('One icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
const storage = getStorage(provider, prefix) as IconStorageWithCache;
// Add one icon set
const icon: IconifyJSON = {
@ -49,6 +52,7 @@ describe('Testing saving to localStorage', () => {
// Check icon storage
expect(iconExists(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Counter should be 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@ -62,6 +66,9 @@ describe('Testing saving to localStorage', () => {
// Storing in cache should not add item to storage
expect(iconExists(storage, 'foo')).toBe(false);
// lastModified is missing, so should not have updated
expect(storage.lastModifiedCached).toBeUndefined();
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
@ -89,11 +96,13 @@ describe('Testing saving to localStorage', () => {
it('Multiple icon sets', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified,
icons: {
foo0: {
body: '<g></g>',
@ -107,6 +116,7 @@ describe('Testing saving to localStorage', () => {
};
const icon1: IconifyJSON = {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
@ -128,6 +138,7 @@ describe('Testing saving to localStorage', () => {
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
@ -144,6 +155,9 @@ describe('Testing saving to localStorage', () => {
session: new Set(),
});
// lastModified should be set
expect(storage.lastModifiedCached).toBe(lastModified);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
2
@ -160,6 +174,166 @@ describe('Testing saving to localStorage', () => {
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Multiple icon sets, first is outdated', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified1 = 1234;
const lastModified2 = 12345;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified: lastModified1,
icons: {
foo0: {
body: '<g></g>',
},
},
};
const item0: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon0,
};
// lastModified is newer than first entry: first entry should be deleted
const icon1: IconifyJSON = {
prefix: prefix,
lastModified: lastModified2,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item1: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon1,
};
// Set cache
reset({
localStorage: cache,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
storeInBrowserStorage(storage, icon1);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// lastModified should be set to max value
expect(storage.lastModifiedCached).toBe(lastModified2);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
// Second item!
JSON.stringify(item1)
);
expect(cache.getItem(browserCachePrefix + '1')).toBeFalsy();
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Multiple icon sets, second set is outdated', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified1 = 12345;
const lastModified2 = 1234;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified: lastModified1,
icons: {
foo0: {
body: '<g></g>',
},
},
};
const item0: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon0,
};
// Icon set with lastModified lower than previous entry should not be stored
const icon1: IconifyJSON = {
prefix: prefix,
lastModified: lastModified2,
icons: {
foo: {
body: '<g></g>',
},
},
};
// Set cache
reset({
localStorage: cache,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
storeInBrowserStorage(storage, icon1);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// lastModified should be set to maximum value
expect(storage.lastModifiedCached).toBe(lastModified1);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item0)
);
expect(cache.getItem(browserCachePrefix + '1')).toBeFalsy();
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Adding icon set on unused spot', () => {
const prefix = nextPrefix();
const cache = createCache();