UNPKG

captivate-chat-api

Version:

This is a wrapper for captivate chat api socket custom channel

381 lines (338 loc) 12.5 kB
interface Action { id: string; data: any; } /** * Represents a conversation session, handling WebSocket communication and event management. */ export class Conversation { private conversationId: string; private socket: WebSocket; private listeners: Map<string, Function[]>; private metadata: object | null; private local_id: Number; /** * Initializes a new Conversation instance. * @param conversationId - The unique identifier of the conversation. * @param socket - The WebSocket instance for communication. */ constructor(conversationId: string, socket: WebSocket, metadata?: object) { this.conversationId = conversationId; this.socket = socket; this.listeners = new Map(); this.metadata = metadata || null; // If metadata is provided, use it; otherwise, set to null. this.local_id = Math.floor(Math.random() * 10000); // Simple random id for local instance tracking this.socket.onmessage = this.handleMessage.bind(this); } private handleMessage(event: MessageEvent) { const message = JSON.parse(event.data); const eventType = message.event?.event_type; if (eventType && this.listeners.has(eventType)) { const payload = message.event.event_payload; this.listeners.get(eventType)?.forEach((callback) => callback(payload)); } } public restartListeners() { // Listen to WebSocket messages and handle events. this.socket.onmessage = this.handleMessage.bind(this); } /** * Adds an event listener for a specific event type. * @param eventType - The type of event to listen for. * @param callback - The function to invoke when the event occurs. */ private addListener(eventType: string, callback: Function): void { this.restartListeners(); if (!this.listeners.has(eventType)) { this.listeners.set(eventType, []); } this.listeners.get(eventType)?.push(callback); } /** * Registers a listener for messages from the bot or live chat. * @param callback - The function to handle incoming messages. */ public onMessage(callback: (message: string, type: string) => void): void { this.addListener('bot_message', (payload: any) => callback(payload.content, 'ai_agent')); this.addListener('livechat_message', (payload: any) => callback(payload.content, 'human_agent')); } /** * Registers a listener for receiving actions. * @param callback - The function to handle incoming action. */ public onActionReceived(callback: (actions: [Action]) => void): void { this.addListener('action', (payload: any) => callback(payload.actions)); } /** * Registers a listener for updates to the conversation. * @param callback - The function to handle conversation updates. */ public onConversationUpdate(callback: (update: any) => void): void { this.addListener('conversation_update', (payload: any) => { callback({ type: payload.type, conversationId: payload.conversation_id, data: payload.data, }); }); } /** * Registers a listener for error events. * @param callback - The function to handle errors. */ public onError(callback: (error: any) => void): void { this.addListener('general_error', (payload: any) => { callback({ conversationId: payload.conversation_id, errorCode: payload.error_code, errorDesc: payload.error_desc, }); }); } /** * Sends a message to the conversation. * @param content - The message content to send. * @returns A promise that resolves when the message is sent. */ public async sendMessage(content: object | string): Promise<void> { // Check if content is a string and transform it into a default object if (typeof content === 'string') { content = { type: 'text', text: content }; } return this.sendPayload('user_message', { type: 'message_create', client_msg_id: `unique-message-id-${Date.now()}`, conversation_id: this.conversationId, content, }); } /** * Sets metadata for the conversation and listens for success confirmation. * @param metadata - An object containing the metadata to set. * @returns A promise that resolves when the metadata update is successful. */ public async setMetadata(metadata: object): Promise<void> { if (typeof metadata !== 'object' || metadata === null) { throw new Error('Metadata must be a non-null object.'); } return new Promise((resolve, reject) => { const metadataRequest = { action: 'sendMessage', event: { event_type: 'metadata', event_payload: { metadata, client_msg_id: `metadata-${Date.now()}`, conversation_id: this.conversationId, }, }, }; // Send the metadata update request this.socket.send(JSON.stringify(metadataRequest)); // Listener function for metadata update success const onMetadataSuccess = (payload: any) => { if (payload.conversation_id === this.conversationId) { this.removeListener('metadata_update_success', onMetadataSuccess); resolve(payload); } }; // Add the success listener this.addListener('metadata_update_success', onMetadataSuccess); // Timeout for failure case setTimeout(() => { this.removeListener('metadata_update_success', onMetadataSuccess); reject(new Error('Timeout: No response for metadata update')); }, 15000); }); } /** * Sends an action to the conversation. * @param actionId - The unique ID of the action to send. * @param data - Optional data associated with the action. * @returns A promise that resolves when the action is sent. */ public async sendAction(actionId: string, data: object = {}): Promise<void> { return this.sendPayload('action', { type: 'normal', id: actionId, data, conversation_id: this.conversationId, }); } /** * Requests the transcript of the conversation. * @returns A promise that resolves to the conversation transcript. */ public async getTranscript(): Promise<object[]> { return new Promise((resolve, reject) => { const transcriptRequest = { action: 'sendMessage', event: { event_type: 'conversation_transcript_request', event_payload: { conversation_id: this.conversationId, }, }, }; this.socket.send(JSON.stringify(transcriptRequest)); const onMessage = (payload: any) => { if (payload.conversation_id === this.conversationId) { this.removeListener('conversation_transcript', onMessage); resolve(payload.transcript); } }; this.addListener('conversation_transcript', onMessage); setTimeout(() => { this.removeListener('conversation_transcript', onMessage); reject(new Error('Timeout: No response for transcript request')); }, 10000); }); } /** * Requests metadata for the conversation. * @returns A promise that resolves to the conversation metadata. */ public async getMetadata(): Promise<object> { return new Promise((resolve, reject) => { const metadataRequest = { action: 'sendMessage', event: { event_type: 'metadata_request', event_payload: { conversation_id: this.conversationId, }, }, }; this.socket.send(JSON.stringify(metadataRequest)); const onMessage = (payload: any) => { if (payload.conversation_id === this.conversationId) { this.removeListener('conversation_metadata', onMessage); resolve(payload.content); } }; this.addListener('conversation_metadata', onMessage); setTimeout(() => { this.removeListener('conversation_metadata', onMessage); reject(new Error('Timeout: No response for metadata request')); }, 10000); }); } /** * Deletes this conversation. * @returns A promise that resolves when the conversation is deleted successfully. */ public async delete(): Promise<void> { return new Promise((resolve, reject) => { try { const deleteRequest = { action: 'sendMessage', event: { event_type: 'delete_conversation', event_payload: { conversation_id: this.conversationId }, }, }; this.socket.send(JSON.stringify(deleteRequest)); const onDeleteSuccess = (event: MessageEvent) => { const message = JSON.parse(event.data); if ( message.event?.event_type === 'delete_conversation_success' && message.event.event_payload.conversation_id === this.conversationId ) { this.socket.removeEventListener('message', onDeleteSuccess); resolve(); } }; this.socket.addEventListener('message', onDeleteSuccess); setTimeout(() => { this.socket.removeEventListener('message', onDeleteSuccess); reject(new Error(`Timeout: No response for deleting conversation ${this.conversationId}`)); }, 10000); } catch (error) { reject(error); } }); } /** * Edits a message in the conversation. * @param messageId - The ID of the message to edit. * @param content - The new content for the message (object or string). * @returns A promise that resolves when the edit is confirmed by user_message_updated. */ public async editMessage(messageId: string, content: object | string): Promise<void> { // If content is a string, wrap it in a default object if (typeof content === 'string') { content = { type: 'text', text: content }; } return new Promise((resolve, reject) => { // Send the edit_message payload this.sendPayload('edit_message', { type: 'message_create', client_msg_id: `edit-message-id-${Date.now()}`, conversation_id: this.conversationId, message_id: messageId, content, }); // Listener for message_edited_success event const onEditSuccess = (payload: any) => { console.log(payload); if ( payload.conversation_id === this.conversationId && payload.message_id === messageId ) { this.removeListener('message_edited_success', onEditSuccess); resolve(); } }; this.addListener('message_edited_success', onEditSuccess); // Timeout for failure case setTimeout(() => { this.removeListener('message_edited_success', onEditSuccess); reject(new Error('Timeout: No response for message edit')); }, 10000); }); } /** * Sends a payload to the WebSocket. * @param eventType - The type of event being sent. * @param payload - The payload data to include with the event. * @returns A promise that resolves when the payload is sent. */ private async sendPayload(eventType: string, payload: object): Promise<void> { return new Promise((resolve, reject) => { try { const message = { action: 'sendMessage', event: { event_type: eventType, event_payload: payload, }, }; this.socket.send(JSON.stringify(message)); resolve(); } catch (error) { reject(error); } }); } /** * Removes a specific event listener. * @param eventType - The type of event. * @param callback - The callback function to remove. */ private removeListener(eventType: string, callback: Function): void { const callbacks = this.listeners.get(eventType); if (callbacks) { this.listeners.set( eventType, callbacks.filter((cb) => cb !== callback) ); } } /** * Gets the unique identifier of the conversation. * @returns The conversation ID. */ public getConversationId(): string { return this.conversationId; } }