127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import {
|
|
Engine,
|
|
type XOEngineOptions,
|
|
// This is temporary. Will likely be moved to where we import templates in the cli. I think that makes more sense as this is a library thing
|
|
generateTemplateIdentifier,
|
|
} from '@xo-cash/engine';
|
|
import type { XOInvitation } from '@xo-cash/types';
|
|
|
|
import { Invitation } from './invitation.js';
|
|
import { Storage } from './storage.js';
|
|
import { SyncServer } from '../utils/sync-server.js';
|
|
import { HistoryService } from './history.js';
|
|
|
|
import { EventEmitter } from '../utils/event-emitter.js';
|
|
|
|
// TODO: Remove this. Exists to hash the seed for database namespace.
|
|
import { createHash } from 'crypto';
|
|
import { p2pkhTemplate } from '@xo-cash/templates';
|
|
|
|
export type AppEventMap = {
|
|
'invitation-added': Invitation;
|
|
'invitation-removed': Invitation;
|
|
}
|
|
|
|
export interface AppConfig {
|
|
syncServerUrl: string;
|
|
engineConfig: XOEngineOptions;
|
|
invitationStoragePath: string;
|
|
}
|
|
|
|
export class AppService extends EventEmitter<AppEventMap> {
|
|
public engine: Engine;
|
|
public storage: Storage;
|
|
public config: AppConfig;
|
|
public history: HistoryService;
|
|
|
|
public invitations: Invitation[] = [];
|
|
|
|
static async create(seed: string, config: AppConfig): Promise<AppService> {
|
|
// Because of a bug that lets wallets read the unspents of other wallets, we are going to manually namespace the storage paths for the app.
|
|
// We are going to do this by computing a hash of the seed and prefixing the storage paths with it.
|
|
const seedHash = createHash('sha256').update(seed).digest('hex');
|
|
|
|
// We want to only prefix the file name
|
|
const prefixedStoragePath = `${seedHash.slice(0, 8)}-${config.engineConfig.databaseFilename}`;
|
|
|
|
// Create the engine
|
|
const engine = await Engine.create(seed, {
|
|
...config.engineConfig,
|
|
databaseFilename: prefixedStoragePath,
|
|
});
|
|
|
|
// TODO: We *technically* dont want this here, but we also need some initial templates for the wallet, so im doing it here
|
|
// Import the default P2PKH template
|
|
await engine.importTemplate(p2pkhTemplate);
|
|
|
|
// Set default locking parameters for P2PKH
|
|
await engine.setDefaultLockingParameters(
|
|
generateTemplateIdentifier(p2pkhTemplate),
|
|
'receiveOutput',
|
|
'receiver',
|
|
);
|
|
|
|
// Create our own storage for the invitations
|
|
const storage = await Storage.create(config.invitationStoragePath);
|
|
|
|
// Create the app service
|
|
return new AppService(engine, storage, config);
|
|
}
|
|
|
|
constructor(engine: Engine, storage: Storage, config: AppConfig) {
|
|
super();
|
|
|
|
this.engine = engine;
|
|
this.storage = storage;
|
|
this.config = config;
|
|
this.history = new HistoryService(engine, this.invitations);
|
|
}
|
|
|
|
async createInvitation(invitation: XOInvitation | string): Promise<Invitation> {
|
|
// Make sure the engine has the template imported
|
|
const invitationStorage = this.storage.child('invitations')
|
|
const invitationSyncServer = new SyncServer(this.config.syncServerUrl, typeof invitation === 'string' ? invitation : invitation.invitationIdentifier);
|
|
|
|
const deps = {
|
|
engine: this.engine,
|
|
syncServer: invitationSyncServer,
|
|
storage: invitationStorage,
|
|
};
|
|
|
|
// Create the invitation
|
|
const invitationInstance = await Invitation.create(invitation, deps);
|
|
|
|
// Add the invitation to the invitations array
|
|
await this.addInvitation(invitationInstance);
|
|
|
|
return invitationInstance;
|
|
}
|
|
|
|
async addInvitation(invitation: Invitation): Promise<void> {
|
|
// Add the invitation to the invitations array
|
|
this.invitations.push(invitation);
|
|
|
|
// Emit the invitation-added event
|
|
this.emit('invitation-added', invitation);
|
|
}
|
|
|
|
async removeInvitation(invitation: Invitation): Promise<void> {
|
|
// Remove the invitation from the invitations array
|
|
this.invitations = this.invitations.filter(i => i !== invitation);
|
|
|
|
// Emit the invitation-removed event
|
|
this.emit('invitation-removed', invitation);
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
// Get the invitations db
|
|
const invitationsDb = this.storage.child('invitations');
|
|
|
|
// Load invitations from storage
|
|
const invitations = await invitationsDb.all() as { key: string; value: XOInvitation }[];
|
|
|
|
await Promise.all(invitations.map(async ({ key }) => {
|
|
await this.createInvitation(key);
|
|
}));
|
|
}
|
|
} |