ui-framework-jps
Version:
A simple UI framework for state management and UI components
343 lines • 17.2 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import debug from 'debug';
import moment from "moment";
import { v4 } from "uuid";
import { DataChangeType } from "../../socket/SocketListener";
import { IndexedDBStateManager } from "../implementation/IndexedDBStateManager";
import { SecurityManager } from "../../security/SecurityManager";
import { SocketManager } from "../../socket/SocketManager";
import { ApiUtil } from "../../network/ApiUtil";
import { ObjectDefinitionRegistry } from "../../model/ObjectDefinitionRegistry";
import { EncryptedIndexedDBStateManager } from "../implementation/EncryptedIndexedDBStateManager";
const logger = debug('persistent-local-cache');
export class PersistentLocalCache {
constructor() {
this.cacheConfig = [];
this.dbName = '';
this.refreshDates = {};
this.hasLoadedUpdatedDates = false;
this.hasLoadedCache = false;
this.hasLoadedRefreshDates = false;
this.lastUpdatedDates = [];
this.isLoadingLastUpdateDates = false;
this.cache = new IndexedDBStateManager();
this.encryptedCache = new EncryptedIndexedDBStateManager();
this.callbackForLastUpdatedDates = this.callbackForLastUpdatedDates.bind(this);
this.loadLastUpdatedDates();
ObjectDefinitionRegistry.getInstance().addDefinition(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED, 'Local Cache', true, false, false, "_id");
}
static getInstance() {
if (!(PersistentLocalCache._instance)) {
PersistentLocalCache._instance = new PersistentLocalCache();
}
return PersistentLocalCache._instance;
}
loadLastUpdatedDates() {
if (this.isLoadingLastUpdateDates)
return;
this.isLoadingLastUpdateDates = true;
logger(`Getting last updated dates`);
let request = {
url: PersistentLocalCache.DEFAULT_URL_FOR_LAST_UPDATED_DATES,
body: {},
callback: this.callbackForLastUpdatedDates
};
ApiUtil.getInstance().simplePOSTJSON(request);
}
stateChanged(managerName, name, newValue) {
switch (managerName) {
case 'indexeddb': {
if (name === PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED) {
logger(`Loading last refresh dates`);
logger(newValue);
const nowAsNumber = parseInt(moment().format('YYYYMMDDHHmmss'));
if (newValue.length > 0) {
this.refreshDates = newValue[0];
this.cacheConfig.forEach((config) => {
const lastRefresh = this.refreshDates[config.name];
// when was the state last updated?
const foundIndex = this.lastUpdatedDates.findIndex((value) => value.name === config.name);
let lastUpdated = 0;
if (foundIndex >= 0) {
lastUpdated = this.lastUpdatedDates[foundIndex].lastUpdated;
logger(`collection ${config.name} last updated at server ${lastUpdated}`);
}
if (lastRefresh) {
// check to see if we need an update
logger(`collection ${config.name} last refreshed ${lastRefresh}`);
const lastRefreshPlusInterval = parseInt(moment(lastRefresh, 'YYYYMMDDHHmmss').add(config.refreshInterval, 'seconds').format('YYYYMMDDHHmmss'));
if (lastRefreshPlusInterval < nowAsNumber) {
logger(`Refresh interval expired, should refresh`);
localStorage.removeItem(this.getLocalStorageKey(config.name));
config.lastRefreshed = nowAsNumber;
this.refreshDates[config.name] = nowAsNumber;
config.shouldRefresh = true;
}
if (lastUpdated > lastRefresh) {
logger(`Last updated at server is after last refresh`);
localStorage.removeItem(this.getLocalStorageKey(config.name));
config.lastRefreshed = nowAsNumber;
this.refreshDates[config.name] = nowAsNumber;
config.shouldRefresh = true;
}
}
else {
logger(`No refresh, setting to now`);
localStorage.removeItem(this.getLocalStorageKey(config.name));
config.lastRefreshed = nowAsNumber;
this.refreshDates[config.name] = nowAsNumber;
config.shouldRefresh = true;
}
});
}
else {
logger(`No refresh dates, setting to now`);
this.refreshDates['_id'] = v4();
this.cacheConfig.forEach((config) => {
localStorage.removeItem(this.getLocalStorageKey(config.name));
config.lastRefreshed = nowAsNumber;
this.refreshDates[config.name] = nowAsNumber;
config.shouldRefresh = true;
});
}
logger(`New refresh dates are:`);
logger(this.refreshDates);
this.cache.updateItemInCollection(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED, this.refreshDates, "_id");
this.hasLoadedRefreshDates = true;
this.loadCache();
}
else {
// is this one of the states we are monitoring
const foundIndex = this.cacheConfig.findIndex((config) => config.name === name);
if (foundIndex >= 0) {
const config = this.cacheConfig[foundIndex];
logger(`Collection ${config.name} received from local cache - loading into application state`);
this.applicationState.setStateByName(config.name, newValue, true);
}
}
break;
}
default: { // remote source
// is this one of the states we are monitoring
const foundIndex = this.cacheConfig.findIndex((config) => config.name === name);
if (foundIndex >= 0) {
const config = this.cacheConfig[foundIndex];
const previouslyLoaded = localStorage.getItem(this.getLocalStorageKey(config.name));
if (config.shouldRefresh || (!(previouslyLoaded))) {
config.shouldRefresh = false;
config.lastRefreshed = parseInt(moment().format('YYYYMMDDHHmmss'));
localStorage.setItem(this.getLocalStorageKey(config.name), `${config.lastRefreshed}`);
this.refreshDates[config.name] = config.lastRefreshed;
logger(`Collection ${config.name} received from remote source - loading into cache`);
if (config.encrypt) {
this.encryptedCache.setStateByName(config.name, newValue, false);
}
else {
this.cache.setStateByName(config.name, newValue, false);
}
logger(`Updating last refreshed for ${config.name} to now`);
logger(this.refreshDates);
this.cache.updateItemInCollection(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED, this.refreshDates, "_id");
}
else {
logger(`Already received ${config.name} from cache`);
}
}
}
}
}
getListenerName() {
return 'Persistent Cache';
}
addCollectionToCacheConfiguration(collectionName, keyField, source, refreshInSeconds, encrypt = false) {
this.cacheConfig.push({
name: collectionName,
keyField: keyField,
source: source,
refreshInterval: refreshInSeconds,
lastRefreshed: 0,
shouldRefresh: false,
encrypt: encrypt
});
if (!encrypt) {
this.cache.addChangeListenerForName(collectionName, this);
}
else {
this.encryptedCache.addChangeListenerForName(collectionName, this);
}
source.addChangeListenerForName(collectionName, this);
}
initialiseAfterCollectionsAddedToCacheConfiguration(dbName, applicationState) {
return __awaiter(this, void 0, void 0, function* () {
this.applicationState = applicationState;
this.dbName = dbName;
const collections = [];
const encryptedCollections = [];
this.cacheConfig.forEach((config) => {
if (config.encrypt) {
encryptedCollections.push({
name: config.name,
keyField: config.keyField
});
}
else {
collections.push({
name: config.name,
keyField: config.keyField
});
}
});
// add the collection for the refresh dates
collections.push({
name: PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED,
keyField: '_id'
});
this.cache.addChangeListenerForName(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED, this);
const result = yield this.cache.initialise(dbName, collections);
if (encryptedCollections.length > 0)
yield this.encryptedCache.initialise(dbName, encryptedCollections);
// load the last refresh dates
this.cache.getStateByName(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED);
});
}
isReadyToLoadCache() {
return (this.hasLoadedRefreshDates && this.hasLoadedUpdatedDates);
}
onDocumentLoaded() {
SocketManager.getInstance().addListener(this);
if (this.hasLoadedUpdatedDates) {
if (this.hasLoadedRefreshDates) {
this.loadCache();
}
}
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
stateChangedItemAdded(managerName, name, itemAdded) {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
stateChangedItemRemoved(managerName, name, itemRemoved) {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
stateChangedItemUpdated(managerName, name, itemUpdated, itemNewValue) {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
filterResults(managerName, name, filterResults) {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
foundResult(managerName, name, foundItem) {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
handleMessage(message) {
}
getCurrentUser() {
return SecurityManager.getInstance().getLoggedInUserId();
}
handleDataChangedByAnotherUser(message) {
const stateObj = message.data;
logger(stateObj);
// update our local cache with the new changes (relies on the socket telling us about them!)
try {
const foundIndex = this.cacheConfig.findIndex((config) => config.name === message.stateName);
if (foundIndex >= 0) {
const config = this.cacheConfig[foundIndex];
const nowAsNumber = parseInt(moment().format('YYYYMMDDHHmmss'));
this.refreshDates[config.name] = nowAsNumber;
logger(`Received data message for ${config.name} with message type ${message.type}`);
this.cache.updateItemInCollection(PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED, this.refreshDates, "_id").then(() => {
switch (message.type) {
case DataChangeType.create: {
if (config.encrypt) {
this.encryptedCache.addNewItemToState(config.name, stateObj, false);
}
else {
this.cache.addNewItemToState(config.name, stateObj, false);
}
break;
}
case DataChangeType.update: {
if (config.encrypt) {
this.encryptedCache.updateItemInState(config.name, stateObj, false);
}
else {
this.cache.updateItemInState(config.name, stateObj, false);
}
break;
}
case DataChangeType.delete: {
if (config.encrypt) {
this.encryptedCache.removeItemFromState(config.name, stateObj, false);
}
else {
this.cache.removeItemFromState(config.name, stateObj, false);
}
break;
}
}
});
}
}
catch (err) {
logger(err);
}
}
callbackForLastUpdatedDates(data, status) {
logger(`Callback - Last updated dates`);
if (status === 200) {
logger(`Last updated dates`);
this.hasLoadedUpdatedDates = true;
this.lastUpdatedDates = data;
this.loadCache();
}
}
getLocalStorageKey(collectionName) {
return `${this.dbName}.${PersistentLocalCache.COLLECTION_NAME_PREFIX}${collectionName}`;
}
loadCache() {
logger(`loading cache - already loaded? ${this.hasLoadedCache}, updates dates from server loaded? ${this.hasLoadedUpdatedDates}, refresh dates loaded? ${this.hasLoadedRefreshDates}`);
if (!(this.hasLoadedUpdatedDates))
return;
if (!(this.hasLoadedRefreshDates))
return;
if (this.hasLoadedCache)
return;
this.cacheConfig.forEach((config) => {
// check to see if needs to be refreshed
const cacheLoaded = localStorage.getItem(this.getLocalStorageKey(config.name));
if (cacheLoaded) {
logger(`${config.name} has been previously loaded, needs refresh? ${config.shouldRefresh}`);
if (config.shouldRefresh) {
logger(`Loading ${config.name} from remote source`);
config.source.forceResetForGet(config.name);
config.source.getStateByName(config.name);
}
else {
logger(`Cache for ${config.name} is up to date, getting from cache`);
config.source.setCompletedRun(config.name, []);
if (config.encrypt) {
this.encryptedCache.getStateByName(config.name);
}
else {
this.cache.getStateByName(config.name);
}
}
}
else {
logger(`Loading ${config.name} from remote source`);
config.source.forceResetForGet(config.name);
config.source.getStateByName(config.name);
}
});
}
}
PersistentLocalCache.COLLECTION_NAME_LAST_REFRESHED = 'persistent-local-cache-last-refresh';
PersistentLocalCache.COLLECTION_NAME_PREFIX = 'persistent-local-cache-';
PersistentLocalCache.DEFAULT_URL_FOR_LAST_UPDATED_DATES = '/getlastupdateddates';
//# sourceMappingURL=PersistentLocalCache.js.map