Files
xo-cli/src/controllers/invitation-controller.ts
2026-01-29 07:13:33 +00:00

293 lines
9.1 KiB
TypeScript

/**
* 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<TrackedInvitation> {
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<string> {
// 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<TrackedInvitation> {
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<TrackedInvitation> {
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<TrackedInvitation> {
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<TrackedInvitation> {
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<TrackedInvitation> {
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<TrackedInvitation> {
return this.flowManager.signInvitation(invitationId);
}
/**
* Broadcasts the transaction for an invitation.
* @param invitationId - Invitation ID
* @returns Transaction hash
*/
async broadcastTransaction(invitationId: string): Promise<string> {
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<string[]> {
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();
}
}