/** * Invitation Controller - High-level interface for invitation management. * * Provides a simplified API for the TUI to interact with invitations, * wrapping the InvitationFlowManager and coordinating with the WalletController. */ import { EventEmitter } from 'events'; import type { XOInvitation } from '@xo-cash/types'; import { InvitationFlowManager, type TrackedInvitation, type InvitationState } from '../services/invitation-flow.js'; import type { WalletController } from './wallet-controller.js'; import type { SyncClient } from '../services/sync-client.js'; /** * Events emitted by the invitation controller. */ export interface InvitationControllerEvents { 'invitation-created': (invitationId: string) => void; 'invitation-updated': (invitationId: string) => void; 'invitation-state-changed': (invitationId: string, state: InvitationState) => void; 'error': (error: Error) => void; } /** * Controller for managing invitations in the TUI. */ export class InvitationController extends EventEmitter { /** Flow manager for invitation lifecycle */ private flowManager: InvitationFlowManager; /** Wallet controller reference */ private walletController: WalletController; /** Sync client reference */ private syncClient: SyncClient; /** * Creates a new invitation controller. * @param walletController - Wallet controller instance * @param syncClient - Sync client instance */ constructor(walletController: WalletController, syncClient: SyncClient) { super(); this.walletController = walletController; this.syncClient = syncClient; this.flowManager = new InvitationFlowManager(walletController, syncClient); // Forward events from flow manager this.flowManager.on('invitation-created', (invitation: XOInvitation) => { this.emit('invitation-created', invitation.invitationIdentifier); }); this.flowManager.on('invitation-updated', (invitationId: string) => { this.emit('invitation-updated', invitationId); }); this.flowManager.on('invitation-state-changed', (invitationId: string, state: InvitationState) => { this.emit('invitation-state-changed', invitationId, state); }); this.flowManager.on('error', (_invitationId: string, error: Error) => { this.emit('error', error); }); } // ============================================================================ // Invitation Creation Flow // ============================================================================ /** * Creates a new invitation from a template action. * @param templateIdentifier - Template ID * @param actionIdentifier - Action ID * @returns Created tracked invitation */ async createInvitation( templateIdentifier: string, actionIdentifier: string, ): Promise { return this.flowManager.createInvitation(templateIdentifier, actionIdentifier); } /** * Publishes an invitation to the sync server and starts listening for updates. * @param invitationId - Invitation ID to publish * @returns The invitation ID for sharing */ async publishAndSubscribe(invitationId: string): Promise { // Publish to sync server await this.flowManager.publishInvitation(invitationId); // Subscribe to SSE updates await this.flowManager.subscribeToUpdates(invitationId); return invitationId; } // ============================================================================ // Invitation Import Flow // ============================================================================ /** * Imports an invitation by ID from the sync server. * @param invitationId - Invitation ID to import * @returns Imported tracked invitation */ async importInvitation(invitationId: string): Promise { return this.flowManager.importInvitation(invitationId); } /** * Accepts an imported invitation (joins as participant). * @param invitationId - Invitation ID to accept * @returns Updated tracked invitation */ async acceptInvitation(invitationId: string): Promise { return this.flowManager.acceptInvitation(invitationId); } // ============================================================================ // Invitation Data Operations // ============================================================================ /** * Appends inputs to an invitation. * @param invitationId - Invitation ID * @param inputs - Inputs to add */ async addInputs( invitationId: string, inputs: Array<{ outpointTransactionHash: string; outpointIndex: number; sequenceNumber?: number; }>, ): Promise { return this.flowManager.appendToInvitation(invitationId, { inputs }); } /** * Appends outputs to an invitation. * @param invitationId - Invitation ID * @param outputs - Outputs to add */ async addOutputs( invitationId: string, outputs: Array<{ valueSatoshis?: bigint; lockingBytecode?: Uint8Array; outputIdentifier?: string; roleIdentifier?: string; }>, ): Promise { return this.flowManager.appendToInvitation(invitationId, { outputs }); } /** * Appends variables to an invitation. * @param invitationId - Invitation ID * @param variables - Variables to add */ async addVariables( invitationId: string, variables: Array<{ variableIdentifier: string; value: bigint | boolean | number | string; roleIdentifier?: string; }>, ): Promise { return this.flowManager.appendToInvitation(invitationId, { variables }); } // ============================================================================ // Signing & Broadcasting // ============================================================================ /** * Signs an invitation. * @param invitationId - Invitation ID to sign * @returns Updated tracked invitation */ async signInvitation(invitationId: string): Promise { return this.flowManager.signInvitation(invitationId); } /** * Broadcasts the transaction for an invitation. * @param invitationId - Invitation ID * @returns Transaction hash */ async broadcastTransaction(invitationId: string): Promise { return this.flowManager.broadcastTransaction(invitationId); } // ============================================================================ // Queries // ============================================================================ /** * Gets a tracked invitation by ID. * @param invitationId - Invitation ID * @returns Tracked invitation or undefined */ getInvitation(invitationId: string): TrackedInvitation | undefined { return this.flowManager.get(invitationId); } /** * Gets all tracked invitations. * @returns Array of tracked invitations */ getAllInvitations(): TrackedInvitation[] { return this.flowManager.getAll(); } /** * Gets the invitation data. * @param invitationId - Invitation ID * @returns The XOInvitation or undefined */ getInvitationData(invitationId: string): XOInvitation | undefined { return this.flowManager.get(invitationId)?.invitation; } /** * Gets the state of an invitation. * @param invitationId - Invitation ID * @returns Invitation state or undefined */ getInvitationState(invitationId: string): InvitationState | undefined { return this.flowManager.get(invitationId)?.state; } /** * Gets available roles for an invitation. * @param invitationId - Invitation ID * @returns Array of available role identifiers */ async getAvailableRoles(invitationId: string): Promise { const tracked = this.flowManager.get(invitationId); if (!tracked) { throw new Error(`Invitation not found: ${invitationId}`); } return this.walletController.getAvailableRoles(tracked.invitation); } /** * Gets missing requirements for an invitation. * @param invitationId - Invitation ID * @returns Missing requirements */ async getMissingRequirements(invitationId: string) { const tracked = this.flowManager.get(invitationId); if (!tracked) { throw new Error(`Invitation not found: ${invitationId}`); } return this.walletController.getMissingRequirements(tracked.invitation); } /** * Gets requirements for an invitation. * @param invitationId - Invitation ID * @returns Requirements */ async getRequirements(invitationId: string) { const tracked = this.flowManager.get(invitationId); if (!tracked) { throw new Error(`Invitation not found: ${invitationId}`); } return this.walletController.getRequirements(tracked.invitation); } // ============================================================================ // Cleanup // ============================================================================ /** * Stops tracking an invitation. * @param invitationId - Invitation ID to stop tracking */ stopTracking(invitationId: string): void { this.flowManager.untrack(invitationId); } /** * Cleans up all resources. */ destroy(): void { this.flowManager.destroy(); } }