import { type Federation, Order } from '.'; import { genKey } from '../pgp'; import { systemClient } from '../services/System'; import { saveAsJson } from '../utils'; import Slot from './Slot.model'; type GarageHooks = 'onSlotUpdate'; class Garage { constructor() { this.slots = {}; this.currentSlot = null; this.hooks = { onSlotUpdate: [], }; this.loadSlots(); } slots: Record; currentSlot: string | null; hooks: Record void>>; // Hooks registerHook = (hookName: GarageHooks, fn: () => void): void => { this.hooks[hookName]?.push(fn); }; triggerHook = (hookName: GarageHooks): void => { this.save(); this.hooks[hookName]?.forEach((fn) => { fn(); }); }; // Storage download = (client: 'mobile' | 'web' | 'desktop' | string): void => { const keys = Object.keys(this.slots); saveAsJson(`garage_slots_${new Date().toISOString()}.json`, keys, client); }; save = (): void => { systemClient.setItem('garage_slots', JSON.stringify(this.slots)); if (this.currentSlot) systemClient.setItem('garage_current_slot', this.currentSlot); }; delete = (): void => { this.slots = {}; this.currentSlot = null; systemClient.deleteItem('garage_slots'); systemClient.deleteItem('garage_current_slot'); this.triggerHook('onSlotUpdate'); }; loadSlots = async (): Promise => { this.slots = {}; const slotsDump: string = (await systemClient.getItem('garage_slots')) ?? ''; if (slotsDump !== '') { const rawSlots: Record = JSON.parse(slotsDump); Object.values(rawSlots).forEach((rawSlot: object) => { if (rawSlot?.token) { const robotAttributes = Object.values(rawSlot.robots)[0] as object; this.slots[rawSlot.token] = new Slot( rawSlot.token, Object.keys(rawSlot.robots), { pubKey: robotAttributes?.pubKey, encPrivKey: robotAttributes?.encPrivKey, }, () => { this.triggerHook('onSlotUpdate'); }, ); this.slots[rawSlot.token].updateSlotFromOrder(new Order(rawSlot.lastOrder)); this.slots[rawSlot.token].updateSlotFromOrder(new Order(rawSlot.activeOrder)); } }); this.currentSlot = (await systemClient.getItem('garage_current_slot')) ?? Object.keys(rawSlots)[0]; console.log('Robot Garage was loaded from local storage'); this.triggerHook('onSlotUpdate'); } }; // Slots getSlot: (token?: string) => Slot | null = (token) => { const currentToken = token ?? this.currentSlot; return currentToken ? (this.slots[currentToken] ?? null) : null; }; deleteSlot: (token?: string) => void = (token) => { const targetIndex = token ?? this.currentSlot; if (targetIndex) { Reflect.deleteProperty(this.slots, targetIndex); this.currentSlot = Object.keys(this.slots)[0] ?? null; this.save(); this.triggerHook('onSlotUpdate'); } }; setCurrentSlot: (currentSlot: string) => void = (currentSlot) => { this.currentSlot = currentSlot; this.save(); this.triggerHook('onSlotUpdate'); }; getSlotByOrder: (coordinator: string, orderID: number) => Slot | null = ( coordinator, orderID, ) => { return ( Object.values(this.slots).find((slot) => { const robot = slot.getRobot(coordinator); return slot.activeOrder?.shortAlias === coordinator && robot?.activeOrderId === orderID; }) ?? null ); }; getSlotByNostrPubKey: (nostrHexPubkey: string) => Slot | null = (nostrHexPubkey) => { return ( Object.values(this.slots).find((slot) => { return slot.nostrPubKey === nostrHexPubkey; }) ?? null ); }; // Robots createRobot: (federation: Federation, token: string, skipSelect?: boolean) => Promise = async (federation, token, skipSelect) => { if (!token) return; if (this.getSlot(token) === null) { try { const key = await genKey(token); const robotAttributes = { token, pubKey: key.publicKeyArmored, encPrivKey: key.encryptedPrivateKeyArmored, }; if (!skipSelect) this.setCurrentSlot(token); this.slots[token] = new Slot( token, federation.getCoordinatorsAlias(), robotAttributes, () => { this.triggerHook('onSlotUpdate'); }, ); void this.fetchRobot(federation, token); this.save(); } catch (error) { console.error('Error:', error); } } }; fetchRobot = async (federation: Federation, token: string): Promise => { const slot = this.getSlot(token); if (slot != null) { await slot.fetchRobot(federation); this.save(); this.triggerHook('onSlotUpdate'); } }; // Coordinators syncCoordinator: (federation: Federation, shortAlias: string) => void = ( federation, shortAlias, ) => { Object.values(this.slots).forEach((slot) => { slot.syncCoordinator(federation, shortAlias); }); this.save(); }; } export default Garage;