293 lines
9.1 KiB
TypeScript
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();
|
|
}
|
|
}
|