wallet-core/packages/web-util/src/utils/observable.ts

182 lines
5.7 KiB
TypeScript
Raw Normal View History

2023-04-14 18:07:23 +02:00
export type ObservableMap<K, V> = Map<K, V> & {
onUpdate: (key: string, callback: () => void) => () => void;
};
const UPDATE_EVENT_NAME = "update";
//FIXME: allow different type for different properties
export function memoryMap<T>(): ObservableMap<string, T> {
const obs = new EventTarget();
const theMap = new Map<string, T>();
const theMemoryMap: ObservableMap<string, T> = {
onUpdate: (key, handler) => {
//@ts-ignore
theMemoryMap.size = theMap.length;
obs.addEventListener(`update-${key}`, handler);
obs.addEventListener(`clear`, handler);
return () => {
obs.removeEventListener(`update-${key}`, handler);
obs.removeEventListener(`clear`, handler);
};
},
delete: (key: string) => {
const result = theMap.delete(key);
obs.dispatchEvent(new Event(`update-${key}`));
return result;
},
set: (key: string, value: T) => {
theMap.set(key, value);
obs.dispatchEvent(new Event(`update-${key}`));
return theMemoryMap;
},
clear: () => {
theMap.clear();
obs.dispatchEvent(new Event(`clear`));
},
entries: theMap.entries.bind(theMap),
forEach: theMap.forEach.bind(theMap),
get: theMap.get.bind(theMap),
has: theMap.has.bind(theMap),
keys: theMap.keys.bind(theMap),
size: theMap.size,
values: theMap.values.bind(theMap),
[Symbol.iterator]: theMap[Symbol.iterator],
[Symbol.toStringTag]: "theMemoryMap",
};
return theMemoryMap;
}
export function localStorageMap(): ObservableMap<string, string> {
const obs = new EventTarget();
const theLocalStorageMap: ObservableMap<string, string> = {
onUpdate: (key, handler) => {
//@ts-ignore
theLocalStorageMap.size = localStorage.length;
obs.addEventListener(`update-${key}`, handler);
obs.addEventListener(`clear`, handler);
function handleStorageEvent(ev: StorageEvent) {
if (ev.key === null || ev.key === key) {
handler();
}
}
window.addEventListener("storage", handleStorageEvent);
return () => {
window.removeEventListener("storage", handleStorageEvent);
obs.removeEventListener(`update-${key}`, handler);
obs.removeEventListener(`clear`, handler);
};
},
delete: (key: string) => {
const exists = localStorage.getItem(key) !== null;
localStorage.removeItem(key);
obs.dispatchEvent(new Event(`update-${key}`));
return exists;
},
set: (key: string, v: string) => {
localStorage.setItem(key, v);
obs.dispatchEvent(new Event(`update-${key}`));
return theLocalStorageMap;
},
clear: () => {
localStorage.clear();
obs.dispatchEvent(new Event(`clear`));
},
entries: (): IterableIterator<[string, string]> => {
let index = 0;
const total = localStorage.length;
return {
next() {
const key = localStorage.key(index);
if (key === null) {
//we are going from 0 until last, this should not happen
throw Error("key cant be null");
}
const item = localStorage.getItem(key);
if (item === null) {
//the key exist, this should not happen
throw Error("value cant be null");
}
if (index == total) return { done: true, value: [key, item] };
index = index + 1;
return { done: false, value: [key, item] };
},
[Symbol.iterator]() {
return this;
},
};
},
forEach: (cb) => {
for (let index = 0; index < localStorage.length; index++) {
const key = localStorage.key(index);
if (key === null) {
//we are going from 0 until last, this should not happen
throw Error("key cant be null");
}
const item = localStorage.getItem(key);
if (item === null) {
//the key exist, this should not happen
throw Error("value cant be null");
}
cb(key, item, theLocalStorageMap);
}
},
get: (key: string) => {
const item = localStorage.getItem(key);
if (item === null) return undefined;
return item;
},
has: (key: string) => {
return localStorage.getItem(key) === null;
},
keys: () => {
let index = 0;
const total = localStorage.length;
return {
next() {
const key = localStorage.key(index);
if (key === null) {
//we are going from 0 until last, this should not happen
throw Error("key cant be null");
}
if (index == total) return { done: true, value: key };
index = index + 1;
return { done: false, value: key };
},
[Symbol.iterator]() {
return this;
},
};
},
size: localStorage.length,
values: () => {
let index = 0;
const total = localStorage.length;
return {
next() {
const key = localStorage.key(index);
if (key === null) {
//we are going from 0 until last, this should not happen
throw Error("key cant be null");
}
const item = localStorage.getItem(key);
if (item === null) {
//the key exist, this should not happen
throw Error("value cant be null");
}
if (index == total) return { done: true, value: item };
index = index + 1;
return { done: false, value: item };
},
[Symbol.iterator]() {
return this;
},
};
},
[Symbol.iterator]: function (): IterableIterator<[string, string]> {
return theLocalStorageMap.entries();
},
[Symbol.toStringTag]: "theLocalStorageMap",
};
return theLocalStorageMap;
}