diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8ce4250 Binary files /dev/null and b/.DS_Store differ diff --git a/packages/.DS_Store b/packages/.DS_Store new file mode 100644 index 0000000..ebc27e5 Binary files /dev/null and b/packages/.DS_Store differ diff --git a/packages/adapter-ameyo/lib/callStateManager.ts b/packages/adapter-ameyo/lib/callStateManager.ts index f3413c1..3697448 100644 --- a/packages/adapter-ameyo/lib/callStateManager.ts +++ b/packages/adapter-ameyo/lib/callStateManager.ts @@ -15,7 +15,7 @@ class CallStateManager { private readonly MAX_CONNECTION_ATTEMPTS = 3; private connectionTimeout: ReturnType | null = null; - private sendConnectedMessage() { + public sendConnectedMessage() { const customerInfo = this.state.customerInfo; if (customerInfo?.callId || customerInfo?.phoneNumber) { @@ -94,14 +94,6 @@ class CallStateManager { data: this.state.customerInfo }); break; - - case 'connected': - if (!this.state.isConnected) { - this.state.isConnected = true; - this.sendConnectedMessage(); - } - break; - case 'hungup': this.handleCallDisconnect(); break; diff --git a/packages/adapter-ameyo/lib/htmlTagManager.ts b/packages/adapter-ameyo/lib/htmlTagManager.ts new file mode 100644 index 0000000..e5bf0dd --- /dev/null +++ b/packages/adapter-ameyo/lib/htmlTagManager.ts @@ -0,0 +1,156 @@ +type TagConfig = { + id: string; + type: 'audio' | 'video'; + attributes?: Record; + styles?: Partial; +} + +class TagManager { + private static instance: TagManager; + private initialized: boolean = false; + private readonly scriptUrl = 'https://public-assets.np.navi-gi.in/jarvis/sip5ml.js'; + + private readonly tagConfigs: TagConfig[] = [ + { + id: 'audio_remote', + type: 'audio', + attributes: { + autoplay: true + } + }, + { + id: 'video_local', + type: 'video', + attributes: { + className: 'video', + width: '100%', + height: '100%', + autoplay: true, + muted: true + }, + styles: { + opacity: '0', + display: 'none', + backgroundColor: '#000000', + webkitTransitionProperty: 'opacity', + webkitTransitionDuration: '2s' + } + }, + { + id: 'video_remote', + type: 'video', + attributes: { + className: 'video', + width: '100%', + height: '100%', + autoplay: true + }, + styles: { + opacity: '0', + display: 'none', + backgroundColor: '#000000', + webkitTransitionProperty: 'opacity', + webkitTransitionDuration: '2s' + } + }, + { + id: 'ringtone', + type: 'audio', + attributes: { + loop: true, + src: 'https://public-assets.np.navi-gi.in/jarvis/ringtone.wav' + } + }, + { + id: 'ringbacktone', + type: 'audio', + attributes: { + loop: true, + src: 'https://public-assets.np.navi-gi.in/jarvis/ringbacktone.wav' + } + }, + { + id: 'dtmfTone', + type: 'audio', + attributes: { + src: 'https://public-assets.np.navi-gi.in/jarvis/dtmf.wav' + } + }, + { + id: 'beep', + type: 'audio', + attributes: { + src: 'https://public-assets.np.navi-gi.in/jarvis/beep.wav' + } + } + ]; + + private constructor() {} + + public static getInstance(): TagManager { + if (!TagManager.instance) { + TagManager.instance = new TagManager(); + } + return TagManager.instance; + } + + private createTag(config: TagConfig): HTMLElement { + const element = document.createElement(config.type); + + // Set attributes + if (config.attributes) { + Object.entries(config.attributes).forEach(([key, value]) => { + if (typeof value === 'boolean') { + if (value) element.setAttribute(key, ''); + } else { + element.setAttribute(key, value); + } + }); + } + + // Set styles + if (config.styles) { + Object.assign(element.style, config.styles); + } + + element.id = config.id; + return element; + } + + private areTagsPresent(): boolean { + return this.tagConfigs.every(config => document.getElementById(config.id)); + } + + private appendScript(): Promise { + return new Promise((resolve) => { + const script = document.createElement('script'); + script.src = this.scriptUrl; + script.async = true; + script.onload = () => { + const event = new CustomEvent('onSipSetupReady', { + detail: { message: 'SIP setup loaded' } + }); + window.dispatchEvent(event); + resolve(); + }; + document.head.appendChild(script); + }); + } + + public async initialize(): Promise { + if (this.initialized || this.areTagsPresent()) { + console.log('Tags already initialized, skipping'); + return; + } + + this.tagConfigs.forEach(config => { + const element = this.createTag(config); + document.body.appendChild(element); + }); + + await this.appendScript(); + this.initialized = true; + } +} + +export const tagManager = TagManager.getInstance(); diff --git a/packages/adapter-ameyo/lib/main.ts b/packages/adapter-ameyo/lib/main.ts index 72731a6..414e4eb 100644 --- a/packages/adapter-ameyo/lib/main.ts +++ b/packages/adapter-ameyo/lib/main.ts @@ -1,50 +1,46 @@ -import IAdapter from "@universal-call-sdk/common/lib/Interfaces/IAdapter.ts"; -import GenericObject from "@universal-call-sdk/common/lib/types/GenericObject.ts"; +import IAdapter from "@universal-call-sdk/common/lib/Interfaces/IAdapter"; +import GenericObject from "@universal-call-sdk/common/lib/types/GenericObject"; import { AmeyoInitializationOptions, - CALL_STATES, + CALL_STATES, CallbackFunctions, CallTransferData, RequestKeys, SipAccountInfo, StateType -} from "./types.ts"; -import MessagingType from "../types/MessagingType.ts"; +} from "./types"; import { ameyoHangupUser, attachOmniqueService, autoSelectExtension, getSipAccountInfo, - loginInAmeyo, maintainHeartbeat, + loginInAmeyo, + maintainHeartbeat, selectCampaign, - setAgentActive, setAgentOnBreak, + setAgentActive, + setAgentOnBreak, setAutoStatus, - getCampaignId, ameyoDisposeCall, - getAllAgentsForTransferCall, transferCallToAgent -} from "./api.ts"; + getCampaignId, + ameyoDisposeCall, + getAllAgentsForTransferCall, + transferCallToAgent +} from "./api"; import { acceptSipCall, loadCallOptions, loadCredentials, sipHangUp, sipMuteCall, - sipRegister, sipUnmuteCall -} from "./assets/js/sip5ml.service.ts"; -import registerEventProcessor from "./eventsProcessor.ts"; -import MetricsProcessor from "@universal-call-sdk/common/lib/utils/metricsProcessor.ts"; -import ClickStreamProcessor from "@universal-call-sdk/common/lib/utils/clickStreamProcessor.ts"; - + sipRegister, + sipUnmuteCall +} from "./assets/js/sip5ml.service"; +import registerEventProcessor from "./eventsProcessor"; +import MetricsProcessor from "@universal-call-sdk/common/lib/utils/metricsProcessor"; +import ClickStreamProcessor from "@universal-call-sdk/common/lib/utils/clickStreamProcessor"; +import {tagManager} from "./htmlTagManager"; +import {callStateManager} from "./callStateManager.ts"; +import MessagingType from "../types/MessagingType.ts"; class AmeyoAdapter implements IAdapter { - private callbacks: { - onCallConnected: (data: StateType) => void; - onCallDisconnected: (data: StateType) => void; - onCallIncoming: (data: StateType) => void, - onAdapterReady: () => void, - onAgentAvailabilityChange: (isAgentAvailable: boolean) => void - onForcedLogout: () => void, - onLoginFailed: (err: GenericObject)=>void - onAgentsForCallTransfer: (data: GenericObject) => void - onCallTransferStatus: (data: GenericObject) => void; - }; + private callbacks: CallbackFunctions; private currentCallState: CALL_STATES; private eventListenerUrl: string; private baseUrl: string; @@ -55,19 +51,24 @@ class AmeyoAdapter implements IAdapter { private currentCallMetadata: GenericObject; private sipAccountInfo: GenericObject; private isAgentAvailable: boolean; - private clickStreamProcessor: ClickStreamProcessor ; + private clickStreamProcessor: ClickStreamProcessor; private metricProcessor: MetricsProcessor; + constructor(options: AmeyoInitializationOptions) { - - if(document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', this._appendTags); - } else { - this._appendTags(); - } - + this.validateInitializationOptions(options); this.baseUrl = options.baseUrl; this.eventListenerUrl = options.eventListenerUrl; + this.userName = options.userName; + this.password = options.password; this.currentCallState = CALL_STATES.IDLE; + this.sessionId = ''; + this.campaignId = ''; + this.sipAccountInfo = {}; + this.currentCallMetadata = {}; + this.isAgentAvailable = false; + this.clickStreamProcessor = {} as ClickStreamProcessor; + this.metricProcessor = {} as MetricsProcessor; + this.callbacks = { onCallIncoming: () => { }, @@ -81,385 +82,364 @@ class AmeyoAdapter implements IAdapter { }, onForcedLogout: () => { }, + onLoginFailed: () => { + }, onAgentsForCallTransfer: () => { }, onCallTransferStatus: () => { - }, - onLoginFailed: ()=>{ - } }; - this.sessionId = ''; - this.userName = options.userName; - this.password = options.password; - this.campaignId = ''; - this.sipAccountInfo = {}; - this.currentCallMetadata = {}; - this.isAgentAvailable = false; - this.clickStreamProcessor = {} as ClickStreamProcessor; - this.metricProcessor={} as MetricsProcessor; - window.BASE_AMEYO_URL = this.baseUrl; - window.AMEYO_LOGIN_URL = options.loginUrl + this.setupGlobalVariables(options); + this.initializeTagManager(); } - init(metricProcessor: MetricsProcessor, clickStreamProcessor: ClickStreamProcessor) { - window.addEventListener('message', this._registerMessageListener); - this._initializeAmeyo(); + private validateInitializationOptions(options: AmeyoInitializationOptions): void { + if (!options.baseUrl || !options.eventListenerUrl || !options.userName || !options.password || !options.loginUrl) { + throw new Error('Missing required initialization options'); + } + } + + + + private setupGlobalVariables(options: AmeyoInitializationOptions): void { + window.BASE_AMEYO_URL = this.baseUrl; + window.AMEYO_LOGIN_URL = options.loginUrl; + } + + private initializeTagManager(): void { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => tagManager.initialize()); + } else { + tagManager.initialize(); + } + } + + public init(metricProcessor: MetricsProcessor, clickStreamProcessor: ClickStreamProcessor): void { + window.addEventListener('message', this.handleMessage); + this.initializeAmeyo(); this.metricProcessor = metricProcessor; this.clickStreamProcessor = clickStreamProcessor; - this.metricProcessor?.pushCounterMetric({metricName: 'initSdk', flow: "sdk_init_count"}) + this.metricProcessor?.pushCounterMetric({ + metricName: 'initSdk', + flow: "sdk_init_count" + }); } - - _initializeSipStack = ({accountName, userName, domain = "", password}: SipAccountInfo) => { - const domainOnly = domain?.split?.(':')?.[0]; - const port = domain?.split?.(':')?.[1]; - //initialize sip stack - loadCredentials({accountName, userName, domain, password}); - loadCallOptions(); - sipRegister({domain: domainOnly, port}); - this.metricProcessor?.pushCounterMetric({metricName: 'sipStackInitialised',flow: 'ameyo-sip-stack_init_count'}) - - - } - - _initializeAmeyo = () => { - loginInAmeyo(this.userName?.toLowerCase(), this.password); - - } - - - _getElementFromDomById = (elementId: string): HTMLElement | null => { - return document.getElementById(elementId); + private initializeSipStack = ({accountName, userName, domain = "", password}: SipAccountInfo): void => { + try { + const [domainOnly, port] = domain.split(':'); + loadCredentials({accountName, userName, domain, password}); + loadCallOptions(); + sipRegister({domain: domainOnly, port}); + this.metricProcessor?.pushCounterMetric({ + metricName: 'sipStackInitialised', + flow: 'ameyo-sip-stack_init_count' + }); + } catch (error) { + console.error('Error initializing SIP stack:', error); + this.handleError('SIP_INIT_ERROR', error); + } }; - _onListenForCorsBypassResponse = (payload: GenericObject) => { - if(payload?.data?.requestKey !== RequestKeys.AMEYO_HEARTBEAT && !payload?.data?.err) { + private initializeAmeyo = (): void => { + loginInAmeyo(this.userName.toLowerCase(), this.password); + }; + + private handleMessage = async ({data}: MessageEvent): Promise => { + try { + if (this.shouldTrackMetrics(data.type)) { + this.trackEventMetrics(data.type); + } + + switch (data.type) { + case MessagingType.SET_RESPONSE_WITHOUT_CORS: + await this.handleCorsBypassResponse(data); + break; + case MessagingType.ON_AMEYO_CALL_INCOMING: + this.handleCallIncoming(data); + break; + case MessagingType.ON_AMEYO_CALL_ACCEPTED: + this.handleCallAccepted(data); + break; + case MessagingType.ON_AMEYO_CALL_DISCONNECTED: + await this.handleCallDisconnected(data); + break; + case MessagingType.ON_AMEYO_AGENT_ON_BREAK: + this.handleAgentBreak(); + break; + case MessagingType.ON_AMEYO_FORCED_LOGOUT: + this.callbacks.onForcedLogout(); + break; + case MessagingType.ON_AMEYO_CALL_TRANSFER: + this.callbacks.onCallTransferStatus(data.data); + break; + } + } catch (error) { + console.error('Error handling message:', error); + this.handleError('MESSAGE_HANDLING_ERROR', error); + } + }; + + private shouldTrackMetrics(messageType: string): boolean { + return messageType !== MessagingType.SET_RESPONSE_WITHOUT_CORS && + messageType !== MessagingType.GET_RESPONSE_WITHOUT_CORS; + } + + private trackEventMetrics(messageType: string): void { + this.metricProcessor?.pushCounterMetric({ + metricName: `ameyo-events-count`, + flow: 'api-events-count', + subFlow: `universal-call-sdk-${messageType}` + }); + } + + private handleError(type: string, error: any): void { + this.metricProcessor?.pushCounterMetric({ + metricName: `ameyo-error-${type}`, + flow: 'error-count', + subFlow: type + }); + this.clickStreamProcessor?.sendClickStreamEvent({ + type: 'error', + error: error.message || error + }); + } + + private async handleCorsBypassResponse(payload: GenericObject): Promise { + this.trackApiMetrics(payload); + + switch (payload?.data?.requestKey) { + case RequestKeys.AMEYO_LOGIN: + await this.handleLoginResponse(payload); + break; + case RequestKeys.SIP_ACCOUNT_INFO: + await this.handleSipAccountInfo(payload); + break; + case RequestKeys.GET_CAMPAIGN_ID: + await this.handleCampaignId(payload); + break; + case RequestKeys.AMEYO_AVAILABLE: + this.handleAgentAvailable(); + break; + case RequestKeys.OMNIQUEUE_SERVICE: + await this.handleOmniQueueService(); + break; + case RequestKeys.SELECT_CAMPAIGN: + await this.handleCampaignSelection(); + break; + case RequestKeys.AMEYO_ON_BREAK: + this.handleAgentBreak(); + break; + case RequestKeys.GET_AGENTS_FOR_CALL_TRANSFER: + this.handleAgentsForTransfer(payload); + break; + } + } + + private trackApiMetrics(payload: GenericObject): void { + if (payload?.data?.requestKey !== RequestKeys.AMEYO_HEARTBEAT && !payload?.data?.err) { this.metricProcessor?.pushCounterMetric({ - metricName: `ameyo-api-call-count`, + metricName: 'ameyo-api-call-count', flow: 'api-call-count', subFlow: payload?.data?.requestKey - }) + }); this.metricProcessor?.pushHistogramMetric({ metricName: `ameyo-api-latency-${payload?.data?.requestKey}`, flow: 'api-latency', subFlow: payload?.data?.requestKey, - value: payload?.data?.time/1000 || 0 //converting to seconds - }) - } - - if(payload?.data?.err) { - this.metricProcessor?.pushCounterMetric({metricName: `ameyo-api-err-count`, flow: 'api-error-count', subFlow: payload?.data?.requestKey}) - this.clickStreamProcessor?.sendClickStreamEvent({type: 'api-error', err: payload}); - } - - if (payload?.data?.requestKey === RequestKeys.AMEYO_LOGIN) { - if(payload?.data?.err) { - console.log('on login failed', payload?.data?.err); - this.callbacks.onLoginFailed(payload?.err); - } - const sessionId = payload?.data?.response?.userSessionInfo?.sessionId; - this.sessionId = sessionId; - getSipAccountInfo(sessionId, this.userName?.toLowerCase()); - registerEventProcessor(this.eventListenerUrl, sessionId); - maintainHeartbeat(this.sessionId, window?.listenerName || '', 0); - } - if (payload?.data?.requestKey === RequestKeys.AMEYO_HEARTBEAT) { - } - if (payload?.data?.requestKey === RequestKeys.SIP_ACCOUNT_INFO) { - const response = payload?.data?.response; - this._initializeSipStack({ - accountName: response?.accountName, - userName: response?.userName, - domain: response?.domain, - password: response?.secret + value: payload?.data?.time / 1000 || 0 }); - this.sipAccountInfo = payload?.data?.response; - console.log('sip account info', this.sipAccountInfo) - getCampaignId(this.sessionId); - setAutoStatus(this.sessionId); } - if (payload?.data?.requestKey === RequestKeys.GET_CAMPAIGN_ID) { - this.campaignId = payload?.data?.response?.campaignInfos?.[0]?.campaignId; - attachOmniqueService(this.sessionId, this.userName.toLowerCase(), this.campaignId); - } - if (payload?.data?.requestKey === RequestKeys.AMEYO_AVAILABLE) { - setAutoStatus(this.sessionId); - this.isAgentAvailable = true; - window.postMessage({type: 'onAmeyoAvailabiltyChange', data: true}); - this.callbacks.onAgentAvailabilityChange(true) - } - if (payload?.data?.requestKey === RequestKeys.OMNIQUEUE_SERVICE) { - selectCampaign(this.sessionId, this.userName.toLowerCase(), this.campaignId); - } - if (payload?.data?.requestKey === RequestKeys.SELECT_CAMPAIGN) { - autoSelectExtension(this.sessionId, this.userName.toLowerCase()); - this.callbacks.onAdapterReady(); - this.currentCallState = CALL_STATES.IDLE; - this.callbacks.onAgentAvailabilityChange(true); - window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false},); - } - if (payload?.data?.requestKey === RequestKeys.AMEYO_ON_BREAK) { - setAutoStatus(this.sessionId); - this.isAgentAvailable = false; - this.callbacks.onAgentAvailabilityChange(false); - window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false},); - } - if (payload?.data?.requestKey === RequestKeys.GET_AGENTS_FOR_CALL_TRANSFER) { - this.callbacks.onAgentsForCallTransfer(payload?.data?.response); - } - } - - _registerMessageListener = async ({data}: GenericObject) => { - if(data?.type !== MessagingType.SET_RESPONSE_WITHOUT_CORS && data?.type !== MessagingType.GET_RESPONSE_WITHOUT_CORS) { + if (payload?.data?.err) { this.metricProcessor?.pushCounterMetric({ - metricName: `ameyo-events-count`, - flow: 'api-events-count', - subFlow:`universal-call-sdk-${data?.type}` - }) + metricName: 'ameyo-api-err-count', + flow: 'api-error-count', + subFlow: payload?.data?.requestKey + }); + this.clickStreamProcessor?.sendClickStreamEvent({ + type: 'api-error', + err: payload + }); + } + } + + private async handleLoginResponse(payload: GenericObject): Promise { + if (payload?.data?.err) { + console.log('Login failed:', payload?.data?.err); + this.callbacks.onLoginFailed(payload?.err); + return; } - if (data?.type === MessagingType.SET_RESPONSE_WITHOUT_CORS) { - this._onListenForCorsBypassResponse(data); - } - if (data?.type === MessagingType.ON_AMEYO_CALL_INCOMING) { - this.callbacks.onCallIncoming(data?.data); - this.currentCallState = CALL_STATES.CALL_INCOMING; - this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data} - } - if (data?.type === MessagingType.ON_AMEYO_CALL_ACCEPTED) { - this.callbacks.onCallConnected(data?.data); - this.currentCallState = CALL_STATES.CALL_CONNECTED; - this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data} - } - if (data?.type === MessagingType.ON_AMEYO_CALL_DISCONNECTED) { - this.callbacks.onCallDisconnected(data?.data); - this.currentCallState = CALL_STATES.CALL_DISCONNECTED; - ameyoHangupUser(this.sessionId, this.currentCallMetadata?.userCRTObjectId); - this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data} - const audioElement =this._getElementFromDomById("beep") as HTMLAudioElement - audioElement?.play(); - - } - if (data?.type === MessagingType.ON_AMEYO_AGENT_ON_BREAK) { - this.isAgentAvailable = false; - this.callbacks.onAgentAvailabilityChange(false); - } - if (data?.type === MessagingType.ON_AMEYO_FORCED_LOGOUT) { - this.callbacks.onForcedLogout() - } - if(data?.type === MessagingType.ON_AMEYO_CALL_TRANSFER){ - this.callbacks.onCallTransferStatus(data?.data); - } - }; - - registerOnCallIncoming(callback: (callState: StateType) => void) { - console.log('registerOnCallIncoming'); - this.callbacks.onCallIncoming = callback; + const sessionId = payload?.data?.response?.userSessionInfo?.sessionId; + this.sessionId = sessionId; + await getSipAccountInfo(sessionId, this.userName?.toLowerCase()); + registerEventProcessor(this.eventListenerUrl, sessionId); + maintainHeartbeat(this.sessionId, window?.listenerName || '', 0); } - registerOnCallConnected(callback: (callState: StateType) => void) { - console.log('registerOnCallConnected'); - this.callbacks.onCallConnected = callback; + private async handleSipAccountInfo(payload: GenericObject): Promise { + const response = payload?.data?.response; + this.initializeSipStack({ + accountName: response?.accountName, + userName: response?.userName, + domain: response?.domain, + password: response?.secret + }); + this.sipAccountInfo = payload?.data?.response; + console.log('sip account info', this.sipAccountInfo) + await getCampaignId(this.sessionId); + await setAutoStatus(this.sessionId); } - registerOnCallDisconnected(callback: (callState: StateType) => void) { - console.log('registerOnCallDisconnected'); - this.callbacks.onCallDisconnected = callback; + private async handleCampaignId(payload: GenericObject): Promise { + this.campaignId = payload?.data?.response?.campaignInfos?.[0]?.campaignId; + await attachOmniqueService(this.sessionId, this.userName.toLowerCase(), this.campaignId); } - registerOnAdapterReady(callback: () => void) { - console.log('registerOnAdapterReady'); - this.callbacks.onAdapterReady = callback; + private handleAgentAvailable(): void { + setAutoStatus(this.sessionId); + this.isAgentAvailable = true; + window.postMessage({type: 'onAmeyoAvailabiltyChange', data: true}); + this.callbacks.onAgentAvailabilityChange(true); } - registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean) => void) { - console.log('registerOnAgentAvailabilityChange'); - this.callbacks.onAgentAvailabilityChange = callback; + private async handleOmniQueueService(): Promise { + await selectCampaign(this.sessionId, this.userName.toLowerCase(), this.campaignId); } - registerOnForcedLogoutListener(callback: () => void) { - console.log('registerOnAgentAvailabilityChange'); - this.callbacks.onForcedLogout = callback; + private async handleCampaignSelection(): Promise { + await autoSelectExtension(this.sessionId, this.userName.toLowerCase()); + this.callbacks.onAdapterReady(); + this.currentCallState = CALL_STATES.IDLE; + this.callbacks.onAgentAvailabilityChange(true); + window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false}); } - registerOnLoginFailedListener(callback: ()=>void) { - console.log('register on login failed'); - this.callbacks.onLoginFailed = callback + private handleAgentBreak(): void { + this.isAgentAvailable = false; + this.callbacks.onAgentAvailabilityChange(false); } - registerOnAgentsForCallTransfer(callback: (data: GenericObject) => void) { - console.log('registerOnAgentsForCallTransfer'); - this.callbacks.onAgentsForCallTransfer = callback; + private handleAgentsForTransfer(payload: GenericObject): void { + this.callbacks.onAgentsForCallTransfer(payload?.data?.response); } - registerOnCallTransferStatus(callback: (data: GenericObject) => void) { - this.callbacks.onCallTransferStatus = callback; + private handleCallIncoming(data: GenericObject): void { + this.callbacks.onCallIncoming(data?.data); + this.currentCallState = CALL_STATES.CALL_INCOMING; + this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}; } - acceptCall() { + private handleCallAccepted(data: GenericObject): void { + this.callbacks.onCallConnected(data?.data); + this.currentCallState = CALL_STATES.CALL_CONNECTED; + this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}; + } + + private async handleCallDisconnected(data: GenericObject): Promise { + this.callbacks.onCallDisconnected(data?.data); + this.currentCallState = CALL_STATES.CALL_DISCONNECTED; + await ameyoHangupUser(this.sessionId, this.currentCallMetadata?.userCRTObjectId); + this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}; + + const audioElement = document.getElementById("beep") as HTMLAudioElement; + await audioElement?.play(); + } + + public acceptCall(): void { acceptSipCall(); + callStateManager.sendConnectedMessage(); } - rejectCall() { + public rejectCall(): void { sipHangUp(); - localStorage.removeItem('revEngCustomerInfo'); } - disposeCall() { - ameyoDisposeCall(this.sessionId, this.campaignId, this.currentCallMetadata?.crtObjectId, this.currentCallMetadata?.userCRTObjectId); + public disposeCall(): void { + ameyoDisposeCall( + this.sessionId, + this.campaignId, + this.currentCallMetadata?.crtObjectId, + this.currentCallMetadata?.userCRTObjectId + ); } - setOnBreak() { + public setOnBreak(): void { setAgentOnBreak(this.sessionId); } - setAvailable() { + public setAvailable(): void { setAgentActive(this.sessionId); } - muteCall() { + public muteCall(): void { sipMuteCall(); } - unmuteCall() { + public unmuteCall(): void { sipUnmuteCall(); } - getAgentAvailability() { + public getAgentAvailability(): boolean { return this.isAgentAvailable; } - getLatestCallState() { + public getLatestCallState(): CALL_STATES { return this.currentCallState; } - getAvailableAgentsForCallTransfer() { + public getAvailableAgentsForCallTransfer(): void { getAllAgentsForTransferCall(this.sessionId); } - transferCallToAgent(data: CallTransferData) { - transferCallToAgent(data, + public transferCallToAgent(data: CallTransferData): void { + transferCallToAgent( + data, this.sessionId, this.currentCallMetadata?.crtObjectId, this.currentCallMetadata?.userCRTObjectId, - this.campaignId); + this.campaignId + ); } - private _appendTags: () => void = () => { - const script = document.createElement('script'); - script.src = 'https://public-assets.np.navi-gi.in/jarvis/sip5ml.js'; // Assuming it's placed in the public folder - script.async = true; - document.head.appendChild(script); - const is_already_appended = document.querySelector('#audio_remote') && document.querySelector('#video_local') && document.querySelector('#video_remote') && document.querySelector('#ringtone') && document.querySelector('#ringbacktone') && document.querySelector('#dtmfTone'); - if (is_already_appended) { - console.log('tags already appended skipping') - return; - } - type ElementAttributes = { - id?: string; - className?: string; - width?: string; - height?: string; - autoplay?: boolean; - muted?: boolean; - loop?: boolean; - src?: string; - style?: Partial; - [key: string]: any; - }; + // Event registration methods + public registerOnCallIncoming(callback: (callState: StateType) => void): void { + this.callbacks.onCallIncoming = callback; + } - function createElement( - tag: keyof HTMLElementTagNameMap, - attributes: ElementAttributes = {}, - parent: HTMLElement = document.body - ): HTMLElement { - const element = document.createElement(tag); + public registerOnCallConnected(callback: (callState: StateType) => void): void { + this.callbacks.onCallConnected = callback; + } - Object.keys(attributes).forEach((attr) => { - if (attr === 'style' && attributes.style) { - Object.assign(element.style, attributes.style); - } else if (attr in element) { - (element as any)[attr] = attributes[attr]; - } else { - element.setAttribute(attr, attributes[attr]); - } - }); + public registerOnCallDisconnected(callback: (callState: StateType) => void): void { + this.callbacks.onCallDisconnected = callback; + } - parent.appendChild(element); - return element; - } + public registerOnAdapterReady(callback: () => void): void { + this.callbacks.onAdapterReady = callback; + } - createElement('audio', { - id: 'audio_remote', - autoplay: true, - }); + public registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean) => void): void { + this.callbacks.onAgentAvailabilityChange = callback; + } - createElement('video', { - className: 'video', - width: '100%', - height: '100%', - id: 'video_local', - autoplay: true, - muted: true, - style: { - opacity: '0', - display: 'none', - backgroundColor: '#000000', - webkitTransitionProperty: 'opacity', - webkitTransitionDuration: '2s', - }, - }); + public registerOnForcedLogoutListener(callback: () => void): void { + this.callbacks.onForcedLogout = callback; + } - createElement('video', { - className: 'video', - width: '100%', - height: '100%', - id: 'video_remote', - autoplay: true, - style: { - display: 'none', - opacity: '0', - backgroundColor: '#000000', - webkitTransitionProperty: 'opacity', - webkitTransitionDuration: '2s', - }, - }); - - createElement('audio', { - id: 'ringtone', - loop: true, - src: 'https://public-assets.np.navi-gi.in/jarvis/ringtone.wav', - }); - - createElement('audio', { - id: 'ringbacktone', - loop: true, - src: 'https://public-assets.np.navi-gi.in/jarvis/ringbacktone.wav', - }); - - createElement('audio', { - id: 'dtmfTone', - src: 'https://public-assets.np.navi-gi.in/jarvis/dtmf.wav', - }); - createElement("audio", { - id: "beep", - src: "https://public-assets.np.navi-gi.in/jarvis/beep.wav", - }); - const onSipSetupReadyEvent = new CustomEvent('onSipSetupReady', { - detail: {message: 'Custom page loaded event triggered'} - }); - - script.onload = () => { - window.dispatchEvent(onSipSetupReadyEvent) - - } - - }; + public registerOnLoginFailedListener(callback: () => void): void { + this.callbacks.onLoginFailed = callback; + } + public registerOnAgentsForCallTransfer(callback: (data: GenericObject) => void): void { + this.callbacks.onAgentsForCallTransfer = callback; + } + public registerOnCallTransferStatus(callback: (data: GenericObject) => void): void { + this.callbacks.onCallTransferStatus = callback; + } } export default AmeyoAdapter; diff --git a/packages/adapter-ameyo/lib/types.ts b/packages/adapter-ameyo/lib/types.ts index 332cd7a..5a35f00 100644 --- a/packages/adapter-ameyo/lib/types.ts +++ b/packages/adapter-ameyo/lib/types.ts @@ -64,6 +64,18 @@ export type SipAccountInfo = { password: string } +export type CallbackFunctions = { + onCallConnected: (data: StateType) => void; + onCallDisconnected: (data: StateType) => void; + onCallIncoming: (data: StateType) => void; + onAdapterReady: () => void; + onAgentAvailabilityChange: (isAgentAvailable: boolean) => void; + onForcedLogout: () => void; + onLoginFailed: (err: GenericObject) => void; + onAgentsForCallTransfer: (data: GenericObject) => void; + onCallTransferStatus: (data: GenericObject) => void; +}; + export enum CALL_STATES { CALL_INCOMING = 'CALL_INCOMING',