UNPKG

@anam-ai/js-sdk

Version:

Client side JavaScript SDK for Anam AI

251 lines 10.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { AnamEvent, InternalEvent, SignalMessageAction, ConnectionClosedCode, } from '../types'; const DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 5; const DEFAULT_WS_RECONNECTION_ATTEMPTS = 5; export class SignallingClient { constructor(sessionId, options, publicEventEmitter, internalEventEmitter, apiGatewayConfig) { var _a, _b, _c, _d, _e; this.stopSignal = false; this.sendingBuffer = []; this.wsConnectionAttempts = 0; this.socket = null; this.heartBeatIntervalRef = null; this.publicEventEmitter = publicEventEmitter; this.internalEventEmitter = internalEventEmitter; this.apiGatewayConfig = apiGatewayConfig; if (!sessionId) { throw new Error('Signalling Client: sessionId is required'); } this.sessionId = sessionId; const { heartbeatIntervalSeconds, maxWsReconnectionAttempts, url } = options; this.heartbeatIntervalSeconds = heartbeatIntervalSeconds || DEFAULT_HEARTBEAT_INTERVAL_SECONDS; this.maxWsReconnectionAttempts = maxWsReconnectionAttempts || DEFAULT_WS_RECONNECTION_ATTEMPTS; if (!url.baseUrl) { throw new Error('Signalling Client: baseUrl is required'); } // Construct WebSocket URL (with or without API Gateway) if (((_a = this.apiGatewayConfig) === null || _a === void 0 ? void 0 : _a.enabled) && ((_b = this.apiGatewayConfig) === null || _b === void 0 ? void 0 : _b.baseUrl)) { // Use API Gateway WebSocket URL const gatewayUrl = new URL(this.apiGatewayConfig.baseUrl); const wsPath = (_c = this.apiGatewayConfig.wsPath) !== null && _c !== void 0 ? _c : '/ws'; // Construct gateway WebSocket URL gatewayUrl.protocol = gatewayUrl.protocol.replace('http', 'ws'); gatewayUrl.pathname = wsPath; this.url = gatewayUrl; // Construct the complete target WebSocket URL and pass it as a query parameter const httpProtocol = url.protocol || 'https'; const targetProtocol = httpProtocol === 'http' ? 'ws' : 'wss'; const httpUrl = `${httpProtocol}://${url.baseUrl}`; const targetWsPath = (_d = url.signallingPath) !== null && _d !== void 0 ? _d : '/ws'; // Build complete target URL const targetUrl = new URL(httpUrl); targetUrl.protocol = targetProtocol === 'ws' ? 'ws:' : 'wss:'; if (url.port) { targetUrl.port = url.port; } targetUrl.pathname = targetWsPath; targetUrl.searchParams.append('session_id', sessionId); // Pass complete target URL as query parameter this.url.searchParams.append('target_url', targetUrl.href); } else { // Direct connection to Anam (original behavior) const httpProtocol = url.protocol || 'https'; const initUrl = `${httpProtocol}://${url.baseUrl}`; this.url = new URL(initUrl); this.url.protocol = url.protocol === 'http' ? 'ws:' : 'wss:'; if (url.port) { this.url.port = url.port; } this.url.pathname = (_e = url.signallingPath) !== null && _e !== void 0 ? _e : '/ws'; this.url.searchParams.append('session_id', sessionId); } } stop() { this.stopSignal = true; this.closeSocket(); } connect() { this.socket = new WebSocket(this.url.href); this.socket.onopen = this.onOpen.bind(this); this.socket.onclose = this.onClose.bind(this); this.socket.onerror = this.onError.bind(this); return this.socket; } sendOffer(localDescription) { return __awaiter(this, void 0, void 0, function* () { const offerMessagePayload = { connectionDescription: localDescription, userUid: this.sessionId, // TODO: this should be renamed to session Id on the server }; const offerMessage = { actionType: SignalMessageAction.OFFER, sessionId: this.sessionId, payload: offerMessagePayload, }; this.sendSignalMessage(offerMessage); }); } sendIceCandidate(candidate) { return __awaiter(this, void 0, void 0, function* () { const iceCandidateMessage = { actionType: SignalMessageAction.ICE_CANDIDATE, sessionId: this.sessionId, payload: candidate.toJSON(), }; this.sendSignalMessage(iceCandidateMessage); }); } sendSignalMessage(message) { var _a; if (((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { try { this.socket.send(JSON.stringify(message)); } catch (error) { console.error('SignallingClient - sendSignalMessage: error sending message', error); } } else { this.sendingBuffer.push(message); } } sendTalkMessage(payload) { return __awaiter(this, void 0, void 0, function* () { const chatMessage = { actionType: SignalMessageAction.TALK_STREAM_INPUT, sessionId: this.sessionId, payload: payload, }; this.sendSignalMessage(chatMessage); }); } sendAgentAudioInput(payload) { const message = { actionType: SignalMessageAction.AGENT_AUDIO_INPUT, sessionId: this.sessionId, payload: payload, }; this.sendSignalMessage(message); } sendAgentAudioInputEnd() { const message = { actionType: SignalMessageAction.AGENT_AUDIO_INPUT_END, sessionId: this.sessionId, payload: {}, }; this.sendSignalMessage(message); } closeSocket() { if (this.socket) { this.socket.close(); this.socket = null; } if (this.heartBeatIntervalRef) { clearInterval(this.heartBeatIntervalRef); this.heartBeatIntervalRef = null; } } onOpen() { return __awaiter(this, void 0, void 0, function* () { if (!this.socket) { throw new Error('SignallingClient - onOpen: socket is null'); } try { this.wsConnectionAttempts = 0; this.flushSendingBuffer(); this.socket.onmessage = this.onMessage.bind(this); this.startSendingHeartBeats(); this.internalEventEmitter.emit(InternalEvent.WEB_SOCKET_OPEN); } catch (e) { console.error('SignallingClient - onOpen: error in onOpen', e); this.publicEventEmitter.emit(AnamEvent.CONNECTION_CLOSED, ConnectionClosedCode.SIGNALLING_CLIENT_CONNECTION_FAILURE); } }); } onClose() { return __awaiter(this, void 0, void 0, function* () { this.wsConnectionAttempts += 1; if (this.stopSignal) { return; } if (this.wsConnectionAttempts <= this.maxWsReconnectionAttempts) { this.socket = null; setTimeout(() => { this.connect(); }, 100 * this.wsConnectionAttempts); } else { if (this.heartBeatIntervalRef) { clearInterval(this.heartBeatIntervalRef); this.heartBeatIntervalRef = null; } this.publicEventEmitter.emit(AnamEvent.CONNECTION_CLOSED, ConnectionClosedCode.SIGNALLING_CLIENT_CONNECTION_FAILURE); } }); } onError(event) { if (this.stopSignal) { return; } console.error('SignallingClient - onError: ', event); } flushSendingBuffer() { const newBuffer = []; if (this.sendingBuffer.length > 0) { this.sendingBuffer.forEach((message) => { var _a; if (((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { this.socket.send(JSON.stringify(message)); } else { newBuffer.push(message); } }); } this.sendingBuffer = newBuffer; } onMessage(event) { return __awaiter(this, void 0, void 0, function* () { const message = JSON.parse(event.data); this.internalEventEmitter.emit(InternalEvent.SIGNAL_MESSAGE_RECEIVED, message); }); } startSendingHeartBeats() { if (!this.socket) { throw new Error('SignallingClient - startSendingHeartBeats: socket is null'); } if (this.heartBeatIntervalRef) { console.warn('SignallingClient - startSendingHeartBeats: heartbeat interval already set'); } // send a heartbeat message every heartbeatIntervalSeconds const heartbeatInterval = this.heartbeatIntervalSeconds * 1000; const heartbeatMessage = { actionType: SignalMessageAction.HEARTBEAT, sessionId: this.sessionId, payload: '', }; const heartbeatMessageJson = JSON.stringify(heartbeatMessage); this.heartBeatIntervalRef = setInterval(() => { var _a; if (this.stopSignal) { return; } if (((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { this.socket.send(heartbeatMessageJson); } }, heartbeatInterval); } } //# sourceMappingURL=SignallingClient.js.map