2020-12-25 19:03:15 +00:00
|
|
|
import type { IconifyJSON } from '@iconify/types';
|
2021-09-05 16:09:25 +00:00
|
|
|
import type { CacheIcons, LoadIconsCache } from '../cache';
|
2020-12-22 12:49:02 +00:00
|
|
|
import { getStorage, addIconSet } from '../storage/storage';
|
2022-06-28 16:12:45 +00:00
|
|
|
import {
|
|
|
|
browserCacheCountKey,
|
|
|
|
browserCachePrefix,
|
|
|
|
browserCacheVersion,
|
|
|
|
browserCacheVersionKey,
|
2022-06-28 18:16:08 +00:00
|
|
|
browserStorageCacheExpiration,
|
|
|
|
browserStorageHour,
|
2022-06-28 16:12:45 +00:00
|
|
|
} 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 =
|
2021-09-05 16:09:25 +00:00
|
|
|
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];
|
|
|
|
}
|
2022-04-29 20:30:20 +00:00
|
|
|
} 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 {
|
2022-06-28 16:12:45 +00:00
|
|
|
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;
|
2022-04-29 20:30:20 +00:00
|
|
|
} catch (err) {
|
2022-04-29 19:59:34 +00:00
|
|
|
//
|
2020-04-28 09:47:35 +00:00
|
|
|
}
|
2022-04-29 19:59:34 +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 {
|
2022-06-28 16:12:45 +00:00
|
|
|
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 {
|
2022-06-28 16:12:45 +00:00
|
|
|
storage.setItem(browserCacheVersionKey, browserCacheVersion);
|
2022-04-29 20:30:20 +00:00
|
|
|
} 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++) {
|
2022-06-28 16:12:45 +00:00
|
|
|
storage.removeItem(browserCachePrefix + i.toString());
|
2020-04-28 09:47:35 +00:00
|
|
|
}
|
2022-04-29 20:30:20 +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 => {
|
2022-06-28 16:12:45 +00:00
|
|
|
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 ||
|
2020-05-29 19:08:45 +00:00
|
|
|
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
|
2020-05-29 19:08:45 +00:00
|
|
|
const provider = data.provider;
|
2020-04-28 09:47:35 +00:00
|
|
|
const prefix = data.data.prefix;
|
2020-05-29 19:08:45 +00:00
|
|
|
const storage = getStorage(provider, prefix);
|
2021-10-12 15:19:46 +00:00
|
|
|
valid = addIconSet(storage, data.data).length > 0;
|
2020-04-28 09:47:35 +00:00
|
|
|
}
|
2022-04-29 20:30:20 +00:00
|
|
|
} catch (err) {
|
2020-04-28 09:47:35 +00:00
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!valid) {
|
|
|
|
func.removeItem(name);
|
|
|
|
}
|
|
|
|
return valid;
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Get version
|
2022-06-28 16:12:45 +00:00
|
|
|
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);
|
2022-04-29 20:30:20 +00:00
|
|
|
} 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
|
|
|
|
*/
|
2020-05-29 19:08:45 +00:00
|
|
|
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),
|
2020-05-29 19:08:45 +00:00
|
|
|
provider,
|
2020-04-28 09:47:35 +00:00
|
|
|
data,
|
|
|
|
};
|
2022-06-28 16:12:45 +00:00
|
|
|
func.setItem(
|
|
|
|
browserCachePrefix + index.toString(),
|
|
|
|
JSON.stringify(item)
|
|
|
|
);
|
2022-04-29 20:30:20 +00:00
|
|
|
} catch (err) {
|
2020-04-28 09:47:35 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-02 07:56:56 +00:00
|
|
|
// 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');
|
|
|
|
}
|
|
|
|
};
|