UNPKG

captivate-chat-api

Version:

This is a wrapper for captivate chat api socket custom channel

303 lines (269 loc) 10.5 kB
// Import the Conversation class import { Conversation } from './Conversation'; // Determine the WebSocket implementation based on the environment (browser, Node.js, or React Native) let WebSocketImpl: typeof WebSocket; // Browser environment (web) if (typeof window !== 'undefined' && window.WebSocket) { WebSocketImpl = window.WebSocket; } // React Native environment else if (typeof global !== 'undefined' && global.WebSocket) { WebSocketImpl = global.WebSocket; } // Node.js environment else { try { WebSocketImpl = require('ws'); } catch (error) { throw new Error('WebSocket not available in this environment and ws module could not be loaded'); } } /** * CaptivateChatAPI class for managing conversations through WebSocket connections. */ export class CaptivateChatAPI { private apiKey: string; private mode: 'prod' | 'dev'; private url: string; private socket: InstanceType<typeof WebSocketImpl> | null; private conversations: Map<string, Conversation>; /** * Creates an instance of CaptivateChatAPI. * @param apiKey - The API key for authentication. * @param mode - The mode of operation ('prod' for production or 'dev' for development). */ constructor(apiKey: string, mode: 'prod' | 'dev' = 'prod') { this.apiKey = apiKey; this.mode = mode; this.url = this.mode === 'prod' ? `wss://channel.wss.captivatechat.ai/dev?apiKey=${apiKey}` : `wss://channel-dev.wss.captivatechat.ai/dev?apiKey=${apiKey}`; this.socket = null; this.conversations = new Map(); } /** * Sends a message through the WebSocket connection. * @param message - The message object to send. */ private _send(message: object): void { if (this.socket && this.socket.readyState === WebSocketImpl.OPEN) { this.socket.send(JSON.stringify(message)); } else { console.error('Socket is not open. Message not sent:', message); } } /** * Connects to the WebSocket server and waits for confirmation from the API. * @returns A promise that resolves once the connection is successfully established. */ public async connect(): Promise<void> { return new Promise((resolve, reject) => { try { this.socket = new WebSocketImpl(this.url); const timeoutId = setTimeout(() => { reject(new Error('Connection timeout: socket_connected not received')); }, 10000); this.socket.onopen = () => { console.log('WebSocket connected, waiting for API confirmation...'); }; this.socket.onmessage = (event: MessageEvent) => { try { const message = JSON.parse(event.data.toString()); if (message.event?.event_type === 'socket_connected') { console.log('API Successfully Connected'); clearTimeout(timeoutId); resolve(); } } catch (err) { console.error('Error parsing message:', err); } }; this.socket.onerror = (event: any) => { console.error('WebSocket Error:', event.message || event); clearTimeout(timeoutId); reject(new Error(event.message || 'WebSocket error')); }; this.socket.onclose = (event) => { console.log('WebSocket connection closed with'); if (event.code !== 1000) { console.log('Attempting to reconnect...'); setTimeout(() => this.connect(), 3000); // Reconnect after 3 seconds } }; } catch (error) { reject(error); } }); } /** * Creates a new conversation or starts a bot-first/user-first conversation. * @param userId - The unique identifier for the user. * @param userBasicInfo - Basic information about the user (e.g., name, email). * @param userData - Additional optional user data. * @param autoConversationStart - Mode to auto-start the conversation ('bot-first' or 'user-first'). * @returns A promise that resolves to the created Conversation instance. */ public async createConversation( userId: string, userBasicInfo: object = {}, metadata: object = {}, autoConversationStart: 'bot-first' | 'user-first' = 'bot-first' ): Promise<Conversation> { return new Promise((resolve, reject) => { try { this._send({ action: 'sendMessage', event: { event_type: 'conversation_start', event_payload: { userId, userBasicInfo, metadata }, }, }); const onMessage = (event: MessageEvent) => { try { const message = JSON.parse(event.data.toString()); if (message.event?.event_type === 'conversation_start_success') { const conversationId = message.event.event_payload.conversation_id; this.socket?.removeEventListener('message', onMessage); const conversation = new Conversation(conversationId, this.socket!); this.conversations.set(conversationId, conversation); if (autoConversationStart === 'bot-first') { conversation .sendMessage({ type: 'text', text: '' }) .then(() => resolve(conversation)) .catch(reject); } else { resolve(conversation); } } } catch (err) { console.error('Error processing message:', err); } }; this.socket?.addEventListener('message', onMessage); setTimeout(() => { this.socket?.removeEventListener('message', onMessage); reject(new Error('Timeout: No response for createConversation')); }, 10000); } catch (error) { reject(error); } }); } /** * Retrieves an existing conversation by its ID or creates a new one if not found. * @param conversationId - The unique ID of the conversation. * @returns The Conversation instance associated with the given ID. */ getConversation(conversationId: string): Conversation | void { let conversation = this.conversations.get(conversationId); if (!conversation) { // If conversation is not found, check if socket is initialized if (this.socket !== null) { // If socket is initialized, create the conversation conversation = new Conversation(conversationId, this.socket); this.conversations.set(conversationId, conversation); } else { // Handle the case where socket is not initialized console.error('Socket is not initialized'); throw new Error('WebSocket connection not established'); } } conversation?.restartListeners(); return conversation; } /** * Retrieves user conversations based on their userId and returns actual conversation objects. * @param userId - The unique identifier for the user. * @returns A promise resolving to a list of Conversation instances. */ public async getUserConversations(userId: string): Promise<Conversation[]> { const conversations: Conversation[] = []; return new Promise((resolve, reject) => { try { this._send({ action: 'sendMessage', event: { event_type: 'get_user_conversations', event_payload: { userId, }, }, }); const onMessage = (event: MessageEvent) => { try { const message = JSON.parse(event.data.toString()); if (message.event?.event_type === 'user_conversations') { this.socket?.removeEventListener('message', onMessage); const payload = message.event.event_payload.conversations; // Iterate over the payload array which contains { id, metadata } for (const conv of payload) { const { conversation_id, metadata } = conv; // Destructure the conversation object to get id and metadata if (this.socket !== null) { conversations.push(new Conversation(conversation_id, this.socket, metadata)); } } resolve(conversations); } } catch (err) { console.error('Error processing message:', err); reject(err); } }; this.socket?.addEventListener('message', onMessage); setTimeout(() => { this.socket?.removeEventListener('message', onMessage); reject(new Error('Timeout: No response for getUserConversations')); }, 10000); } catch (error) { reject(error); } }); } /** * Deletes all conversations for a specific user. * @param userId - The unique identifier for the user whose conversations will be deleted. * @returns A promise that resolves when all conversations are successfully deleted. */ public async deleteUserConversations(userId: string): Promise<void> { return new Promise((resolve, reject) => { if (!userId) { return reject(new Error('User ID must be provided.')); } try { this._send({ action: 'sendMessage', event: { event_type: 'delete_conversation', event_payload: { userId: userId }, }, }); const onDeleteSuccess = (event: MessageEvent) => { try { const message = JSON.parse(event.data.toString()); if ( message.event?.event_type === 'delete_conversation_success' && message.event.event_payload.userId === userId ) { this.socket?.removeEventListener('message', onDeleteSuccess); resolve(); } } catch (err) { reject(err); } }; this.socket?.addEventListener('message', onDeleteSuccess); setTimeout(() => { this.socket?.removeEventListener('message', onDeleteSuccess); reject(new Error(`Timeout: No response for deleting conversations for user ${userId}`)); }, 10000); } catch (error) { reject(error); } }); } }