@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
120 lines (100 loc) • 4.21 kB
text/typescript
import type { RecentAddressesState, RecentAddress } from "@ledgerhq/types-live";
import { RecentAddressesArraySchema } from "./entities/recentAddresses";
export const RECENT_ADDRESSES_COUNT_LIMIT = 12;
export type RecentAddressesCache = RecentAddressesState;
export interface RecentAddressesStore {
addAddress(currency: string, address: string, ensName?: string): void;
removeAddress(currency: string, address: string): void;
syncAddresses(cache: RecentAddressesCache): void;
getAddresses(currency: string): RecentAddress[];
}
let recentAddressesStore: RecentAddressesStore | null = null;
export function getRecentAddressesStore(): RecentAddressesStore {
if (recentAddressesStore === null) {
throw new Error(
"Recent addresses store instance is null, please call function setupRecentAddressesStore in application initialization",
);
}
return recentAddressesStore;
}
export function setupRecentAddressesStore(
addressesByCurrency: RecentAddressesCache,
onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void,
): void {
recentAddressesStore = new RecentAddressesStoreImpl(addressesByCurrency, onAddAddressComplete);
}
class RecentAddressesStoreImpl implements RecentAddressesStore {
private addressesByCurrency: RecentAddressesCache = {};
private readonly onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void;
constructor(
addressesByCurrency: RecentAddressesCache,
onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void,
) {
this.addressesByCurrency = this.sanitizeCache(addressesByCurrency);
this.onAddAddressComplete = onAddAddressComplete;
}
private sanitizeCache(cache: RecentAddressesCache): RecentAddressesCache {
const sanitized: RecentAddressesCache = {};
for (const currency in cache) {
const entries = cache[currency];
const result = RecentAddressesArraySchema.safeParse(entries);
sanitized[currency] = result.success ? result.data : [];
}
return sanitized;
}
addAddress(currency: string, address: string, ensName?: string): void {
this.addAddressToCache(currency, address, Date.now(), true, ensName);
}
removeAddress(currency: string, address: string): void {
if (!this.addressesByCurrency[currency]) return;
const addresses = this.addressesByCurrency[currency];
const index = addresses.findIndex(entry => entry.address === address);
if (index !== -1) {
addresses.splice(index, 1);
this.addressesByCurrency[currency] = [...addresses];
this.onAddAddressComplete(this.addressesByCurrency);
}
}
syncAddresses(cache: RecentAddressesCache): void {
const previousAddresses = { ...this.addressesByCurrency };
this.addressesByCurrency = this.sanitizeCache(cache);
for (const currency in previousAddresses) {
const entries = previousAddresses[currency];
for (const entry of entries) {
this.addAddressToCache(currency, entry.address, entry.lastUsed, false, entry.ensName);
}
}
this.onAddAddressComplete(this.addressesByCurrency);
}
getAddresses(currency: string): RecentAddress[] {
const addresses = this.addressesByCurrency[currency];
if (!addresses) return [];
return addresses.filter(
(entry): entry is RecentAddress =>
!!entry && typeof entry.address === "string" && entry.address.length > 0,
);
}
private addAddressToCache(
currency: string,
address: string,
timestamp: number,
shouldTriggerCallback: boolean,
ensName?: string,
): void {
if (!this.addressesByCurrency[currency]) {
this.addressesByCurrency[currency] = [];
}
const addresses = this.addressesByCurrency[currency];
const addressIndex = addresses.findIndex(entry => entry.address === address);
if (addressIndex !== -1) {
addresses.splice(addressIndex, 1);
} else if (addresses.length >= RECENT_ADDRESSES_COUNT_LIMIT) {
addresses.pop();
}
addresses.unshift({ address, lastUsed: timestamp, ensName });
this.addressesByCurrency[currency] = [...addresses];
if (shouldTriggerCallback) {
this.onAddAddressComplete(this.addressesByCurrency);
}
}
}