/** * Ably Real-time Client * * Provides Ably client and helper functions for real-time broadcasting. * Uses Ably REST API for server-side publishing. */ import Ably from 'ably'; import { ABLY_CHANNELS, IssueUpdatePayload, SyncCompletePayload, ChatMessagePayload, IssueEventType, } from '@/lib/types/ably'; // ============================================================================= // Client Singleton // ============================================================================= let ablyClient: Ably.Rest | null = null; function getAblyClient(): Ably.Rest { if (!ablyClient) { const apiKey = process.env.ABLY_API_KEY; if (!apiKey) { throw new Error('ABLY_API_KEY environment variable is not set'); } ablyClient = new Ably.Rest({ key: apiKey }); } return ablyClient; } // ============================================================================= // Channel Helpers // ============================================================================= function getEventsChannel() { return getAblyClient().channels.get(ABLY_CHANNELS.EVENTS); } function getGlobalChatChannel() { return getAblyClient().channels.get(ABLY_CHANNELS.GLOBAL_CHAT); } // ============================================================================= // Issue/PR Event Publishers // ============================================================================= export async function publishIssueEvent( eventType: IssueEventType, issue: { id: string; githubIssueId: number; number: number; title: string; repoName: string; owner: string; repo: string; isPR: boolean; state: string; } ): Promise { const channel = getEventsChannel(); const payload: IssueUpdatePayload = { eventType, issueId: issue.id, githubIssueId: issue.githubIssueId, number: issue.number, title: issue.title, repoName: issue.repoName, owner: issue.owner, repo: issue.repo, isPR: issue.isPR, state: issue.state, timestamp: new Date().toISOString(), }; await channel.publish(eventType, payload); } export async function publishIssueCreated(issue: Parameters[1]): Promise { await publishIssueEvent(issue.isPR ? 'pr_created' : 'issue_created', issue); } export async function publishIssueUpdated(issue: Parameters[1]): Promise { await publishIssueEvent(issue.isPR ? 'pr_updated' : 'issue_updated', issue); } export async function publishIssueDeleted(issue: Parameters[1]): Promise { await publishIssueEvent(issue.isPR ? 'pr_deleted' : 'issue_deleted', issue); } // ============================================================================= // Sync Event Publisher // ============================================================================= export async function publishSyncComplete(stats: { reposProcessed: number; issuesUpdated: number; issuesDeleted: number; }): Promise { const channel = getEventsChannel(); const payload: SyncCompletePayload = { eventType: 'sync_complete', reposProcessed: stats.reposProcessed, issuesUpdated: stats.issuesUpdated, issuesDeleted: stats.issuesDeleted, timestamp: new Date().toISOString(), }; await channel.publish('sync_complete', payload); } // ============================================================================= // Global Chat Publisher // ============================================================================= export async function publishChatMessage(message: { messageId: string; senderId: string; senderUsername: string; senderAvatarUrl?: string; content: string; }): Promise { const channel = getGlobalChatChannel(); const payload: ChatMessagePayload = { messageId: message.messageId, senderId: message.senderId, senderUsername: message.senderUsername, senderAvatarUrl: message.senderAvatarUrl, content: message.content, timestamp: new Date().toISOString(), }; await channel.publish('message', payload); } // ============================================================================= // Ably Token Request (for client-side auth) // ============================================================================= export async function createTokenRequest(clientId: string): Promise { const client = getAblyClient(); return await client.auth.createTokenRequest({ clientId, capability: { [ABLY_CHANNELS.EVENTS]: ['subscribe'], [ABLY_CHANNELS.GLOBAL_CHAT]: ['subscribe', 'publish'], 'OpenTriage-Chat-*': ['subscribe', 'publish'], }, }); } // ============================================================================= // Health Check // ============================================================================= export function isAblyConfigured(): boolean { return !!process.env.ABLY_API_KEY; }