2
0
mirror of https://github.com/iconify/iconify.git synced 2024-10-24 09:32:02 +00:00
iconify/packages/core/src/browser-storage/index.ts

294 lines
5.7 KiB
TypeScript
Raw Normal View History

2020-12-25 19:03:15 +00:00
import type { IconifyJSON } from '@iconify/types';
import type { CacheIcons, LoadIconsCache } from '../cache';
import { getStorage, addIconSet } from '../storage/storage';
import {
browserCacheCountKey,
browserCachePrefix,
browserCacheVersion,
browserCacheVersionKey,
2022-06-28 18:16:08 +00:00
browserStorageCacheExpiration,
browserStorageHour,
} from './config';
2022-06-28 18:16:08 +00:00
import {
browserStorageConfig,
browserStorageEmptyItems,
browserStorageItemsCount,
} from './data';
import type { BrowserStorageConfig, BrowserStorageItem } from './types';
2020-04-28 09:47:35 +00:00
/**
* Flag to check if storage has been loaded
*/
let loaded = false;
/**
* Fake window for unit testing
*/
type FakeWindow = Record<string, typeof localStorage>;
let _window: FakeWindow =
typeof window === 'undefined' ? {} : (window as unknown as FakeWindow);
2020-04-28 09:47:35 +00:00
export function mock(fakeWindow: FakeWindow): void {
loaded = false;
_window = fakeWindow;
}
/**
* Get global
*
* @param key
*/
2022-06-28 18:16:08 +00:00
function getGlobal(
key: keyof BrowserStorageConfig
): typeof localStorage | null {
2020-04-28 09:47:35 +00:00
const attr = key + 'Storage';
try {
if (
_window &&
_window[attr] &&
typeof _window[attr].length === 'number'
) {
return _window[attr];
}
} catch (err) {
2020-04-28 09:47:35 +00:00
//
}
// Failed - mark as disabled
2022-06-28 18:16:08 +00:00
browserStorageConfig[key] = false;
2020-04-28 09:47:35 +00:00
return null;
}
/**
* Change current count for storage
*/
function setCount(
storage: typeof localStorage,
2022-06-28 18:16:08 +00:00
key: keyof BrowserStorageConfig,
2020-04-28 09:47:35 +00:00
value: number
): boolean {
try {
storage.setItem(browserCacheCountKey, value.toString());
2022-06-28 18:16:08 +00:00
browserStorageItemsCount[key] = value;
2020-04-28 09:47:35 +00:00
return true;
} catch (err) {
//
2020-04-28 09:47:35 +00:00
}
return false;
2020-04-28 09:47:35 +00:00
}
/**
* Get current count from storage
*
* @param storage
*/
function getCount(storage: typeof localStorage): number {
const count = storage.getItem(browserCacheCountKey);
2020-04-28 09:47:35 +00:00
if (count) {
const total = parseInt(count);
return total ? total : 0;
}
return 0;
}
/**
* Initialize storage
*
* @param storage
* @param key
*/
function initCache(
storage: typeof localStorage,
2022-06-28 18:16:08 +00:00
key: keyof BrowserStorageConfig
2020-04-28 09:47:35 +00:00
): void {
try {
storage.setItem(browserCacheVersionKey, browserCacheVersion);
} catch (err) {
2020-04-28 09:47:35 +00:00
//
}
setCount(storage, key, 0);
}
/**
* Destroy old cache
*
* @param storage
*/
function destroyCache(storage: typeof localStorage): void {
try {
const total = getCount(storage);
for (let i = 0; i < total; i++) {
storage.removeItem(browserCachePrefix + i.toString());
2020-04-28 09:47:35 +00:00
}
} catch (err) {
2020-04-28 09:47:35 +00:00
//
}
}
/**
* Load icons from cache
*/
export const loadCache: LoadIconsCache = (): void => {
if (loaded) {
return;
}
loaded = true;
// Minimum time
2022-06-28 18:16:08 +00:00
const minTime =
Math.floor(Date.now() / browserStorageHour) -
browserStorageCacheExpiration;
2020-04-28 09:47:35 +00:00
// Load data from storage
2022-06-28 18:16:08 +00:00
function load(key: keyof BrowserStorageConfig): void {
2020-04-28 09:47:35 +00:00
const func = getGlobal(key);
if (!func) {
return;
}
// Get one item from storage
const getItem = (index: number): boolean => {
const name = browserCachePrefix + index.toString();
2020-04-28 09:47:35 +00:00
const item = func.getItem(name);
if (typeof item !== 'string') {
// Does not exist
return false;
}
// Get item, validate it
let valid = true;
try {
// Parse, check time stamp
2022-06-28 18:16:08 +00:00
const data = JSON.parse(item) as BrowserStorageItem;
2020-04-28 09:47:35 +00:00
if (
typeof data !== 'object' ||
typeof data.cached !== 'number' ||
data.cached < minTime ||
typeof data.provider !== 'string' ||
2020-04-28 09:47:35 +00:00
typeof data.data !== 'object' ||
typeof data.data.prefix !== 'string'
) {
valid = false;
} else {
// Add icon set
const provider = data.provider;
2020-04-28 09:47:35 +00:00
const prefix = data.data.prefix;
const storage = getStorage(provider, prefix);
valid = addIconSet(storage, data.data).length > 0;
2020-04-28 09:47:35 +00:00
}
} catch (err) {
2020-04-28 09:47:35 +00:00
valid = false;
}
if (!valid) {
func.removeItem(name);
}
return valid;
};
try {
// Get version
const version = func.getItem(browserCacheVersionKey);
if (version !== browserCacheVersion) {
2020-04-28 09:47:35 +00:00
if (version) {
// Version is set, but invalid - remove old entries
destroyCache(func);
}
// Empty data
initCache(func, key);
return;
}
// Get number of stored items
let total = getCount(func);
for (let i = total - 1; i >= 0; i--) {
if (!getItem(i)) {
// Remove item
if (i === total - 1) {
// Last item - reduce country
total--;
} else {
// Mark as empty
2022-06-28 18:16:08 +00:00
browserStorageEmptyItems[key].push(i);
2020-04-28 09:47:35 +00:00
}
}
}
// Update total
setCount(func, key, total);
} catch (err) {
2020-04-28 09:47:35 +00:00
//
}
}
2022-06-28 18:16:08 +00:00
for (const key in browserStorageConfig) {
load(key as keyof BrowserStorageConfig);
2020-04-28 09:47:35 +00:00
}
};
/**
* Function to cache icons
*/
export const storeCache: CacheIcons = (
provider: string,
data: IconifyJSON
): void => {
2020-04-28 09:47:35 +00:00
if (!loaded) {
loadCache();
}
2022-06-28 18:16:08 +00:00
function store(key: keyof BrowserStorageConfig): boolean {
if (!browserStorageConfig[key]) {
2020-04-28 09:47:35 +00:00
return false;
}
const func = getGlobal(key);
if (!func) {
return false;
}
// Get item index
2022-06-28 18:16:08 +00:00
let index = browserStorageEmptyItems[key].shift();
2020-04-28 09:47:35 +00:00
if (index === void 0) {
// Create new index
2022-06-28 18:16:08 +00:00
index = browserStorageItemsCount[key];
2020-04-28 09:47:35 +00:00
if (!setCount(func, key, index + 1)) {
return false;
}
}
// Create and save item
try {
2022-06-28 18:16:08 +00:00
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
2020-04-28 09:47:35 +00:00
data,
};
func.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
} catch (err) {
2020-04-28 09:47:35 +00:00
return false;
}
return true;
}
// Do not store empty sets
if (!Object.keys(data.icons).length) {
return;
}
// Remove not_found (clone object to keep old object intact)
if (data.not_found) {
data = Object.assign({}, data);
delete data.not_found;
}
2020-04-28 09:47:35 +00:00
// Attempt to store at localStorage first, then at sessionStorage
if (!store('local')) {
store('session');
}
};