Files
call-sdk/packages/adapter-ameyo/lib/main.ts
2025-02-07 15:10:17 +05:30

432 lines
15 KiB
TypeScript

import IAdapter from "@universal-call-sdk/common/lib/Interfaces/IAdapter";
import GenericObject from "@universal-call-sdk/common/lib/types/GenericObject";
import {
AmeyoInitializationOptions,
CALL_STATES, CallbackFunctions,
CallTransferData,
ErrorType,
SipAccountInfo,
StateType
} from "./types";
import {
ameyoHangupUser,
attachOmniqueService,
autoSelectExtension,
getSipAccountInfo,
loginInAmeyo,
maintainHeartbeat,
selectCampaign,
setAgentActive,
setAgentOnBreak,
setAutoStatus,
getCampaignId,
ameyoDisposeCall,
getAllAgentsForTransferCall,
transferCallToAgent, logoutFromAmeyo
} from "./api";
import {
acceptSipCall,
loadCallOptions,
loadCredentials,
sipHangUp,
sipMuteCall,
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 MessagingType from "../types/MessagingType.ts";
import { checkAllPermissions } from "@universal-call-sdk/common/lib/utils/getPermissions.ts";
class AmeyoAdapter implements IAdapter {
private callbacks: CallbackFunctions;
private currentCallState: CALL_STATES;
private eventListenerUrl: string;
private baseUrl: string;
private sessionId: string;
private campaignId: string;
private userName: string;
private password: string;
private currentCallMetadata: GenericObject;
private sipAccountInfo: GenericObject;
private isAgentAvailable: boolean;
private clickStreamProcessor: ClickStreamProcessor;
private metricProcessor: MetricsProcessor;
constructor(options: AmeyoInitializationOptions) {
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: () => {
},
onCallConnected: () => {
},
onCallDisconnected: () => {
},
onAdapterReady: () => {
},
onAgentAvailabilityChange: () => {
},
onForcedLogout: () => {
},
onLoginFailed: () => {
},
onCallTransferStatus: () => {
}
};
this.setupGlobalVariables(options);
this.initializeTagManager();
}
private validateInitializationOptions(options: AmeyoInitializationOptions): void {
if (!options?.baseUrl || !options?.eventListenerUrl || !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"
});
}
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(ErrorType.SIP_INIT_ERROR, error);
}
};
private initializeAmeyo = async (): Promise<void> => {
try {
const loginResponse: GenericObject = await loginInAmeyo(this.userName.toLowerCase(), this.password);
if (!loginResponse) {
console.error("Invalid username or password");
return;
}
this.trackApiMetrics(loginResponse);
const sipInfoResponse: GenericObject | undefined = await this.handleLoginResponse(loginResponse);
if (!sipInfoResponse) {
console.error("Login failed: Invalid response");
return;
}
const campaignIdResponse: GenericObject = await this.handleSipAccountInfo(sipInfoResponse);
await this.handleCampaignId(campaignIdResponse);
await this.handleOmniQueueService();
await this.handleCampaignSelection();
} catch (error) {
console.error("Error during Ameyo initialization:", error);
this.handleError(ErrorType.INITIALIZATION_ERROR, error);
}
};
private handleMessage = async ({data}: MessageEvent): Promise<void> => {
try {
this.trackEventMetrics(data.type);
switch (data.type) {
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_AVAILABILITY_CHANGE:
this.handleAgentAvailabilityChange(data);
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(ErrorType.MESSAGE_HANDLING_ERROR, error);
}
};
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 trackApiMetrics(payload: GenericObject): void {
if (!payload?.data?.err) {
this.metricProcessor?.pushCounterMetric({
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
});
} else {
this.metricProcessor?.pushCounterMetric({
metricName: 'ameyo-api-err-count',
flow: 'api-error-count',
subFlow: payload?.data?.requestKey
});
this.clickStreamProcessor?.sendClickStreamEvent({
type: 'api-error',
err: payload
});
this.handleError(payload?.data?.requestKey, payload?.data?.err);
}
}
private handleLoginResponse = async (payload: GenericObject): Promise<GenericObject | undefined> => {
if (payload?.data?.err) {
console.log('Login failed:', payload?.data?.err);
this.callbacks.onLoginFailed(payload?.err);
return;
}
const sessionId = payload?.data?.response?.userSessionInfo?.sessionId;
this.sessionId = sessionId;
const res = await getSipAccountInfo(sessionId, this.userName?.toLowerCase());
this.trackApiMetrics(res);
registerEventProcessor(this.eventListenerUrl, sessionId);
maintainHeartbeat(this.sessionId, window?.listenerName || '', 0);
return res;
}
private handleSipAccountInfo = async (payload: GenericObject): Promise<GenericObject> => {
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)
const res = await getCampaignId(this.sessionId);
this.trackApiMetrics(res);
await setAutoStatus(this.sessionId);
return res;
}
private handleCampaignId = async (payload: GenericObject): Promise<void> => {
this.campaignId = payload?.data?.response?.campaignInfos?.[0]?.campaignId;
const res = await attachOmniqueService(this.sessionId, this.userName.toLowerCase(), this.campaignId);
this.trackApiMetrics(res);
}
private handleAgentAvailable = (): void => {
setAutoStatus(this.sessionId);
}
private handleAgentBreak = (): void => {
setAutoStatus(this.sessionId);
}
private handleOmniQueueService = async (): Promise<void> => {
const res = await selectCampaign(this.sessionId, this.userName.toLowerCase(), this.campaignId);
this.trackApiMetrics(res);
}
private handleCampaignSelection = async (): Promise<void> => {
const res = await autoSelectExtension(this.sessionId, this.userName.toLowerCase());
this.trackApiMetrics(res);
this.callbacks.onAdapterReady();
this.currentCallState = CALL_STATES.IDLE;
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false});
}
private handleAgentAvailabilityChange(payload :GenericObject): void {
this.isAgentAvailable = !payload?.data?.isOnBreak;
this.callbacks.onAgentAvailabilityChange(this.isAgentAvailable, payload?.data?.reason || '');
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: this.isAgentAvailable,});
}
private handleCallIncoming(data: GenericObject): void {
this.callbacks.onCallIncoming(data?.data);
this.currentCallState = CALL_STATES.CALL_INCOMING;
this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data};
}
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<void> {
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 logOut() {
logoutFromAmeyo(this.sessionId);
}
public acceptCall(): void {
acceptSipCall();
}
public rejectCall(): void {
sipHangUp();
}
public disposeCall(): void {
ameyoDisposeCall(
this.sessionId,
this.campaignId,
this.currentCallMetadata?.crtObjectId,
this.currentCallMetadata?.userCRTObjectId
);
}
public async setOnBreak(): Promise<void> {
const res = await setAgentOnBreak(this.sessionId);
this.trackApiMetrics(res);
this.handleAgentBreak();
}
public async setAvailable(): Promise<void> {
const res = await setAgentActive(this.sessionId);
this.trackApiMetrics(res);
this.handleAgentAvailable();
}
public muteCall(): void {
sipMuteCall();
}
public unmuteCall(): void {
sipUnmuteCall();
}
public getAgentAvailability(): boolean {
return this.isAgentAvailable;
}
public getLatestCallState(): CALL_STATES {
return this.currentCallState;
}
public async getAvailableAgentsForCallTransfer(): Promise<Response> {
return getAllAgentsForTransferCall(this.sessionId);
}
public transferCallToAgent(data: CallTransferData): void {
transferCallToAgent(
data,
this.sessionId,
this.currentCallMetadata?.crtObjectId,
this.currentCallMetadata?.userCRTObjectId,
this.campaignId
);
}
// Event registration methods
public registerOnCallIncoming(callback: (callState: StateType) => void): void {
this.callbacks.onCallIncoming = callback;
}
public registerOnCallConnected(callback: (callState: StateType) => void): void {
this.callbacks.onCallConnected = callback;
}
public registerOnCallDisconnected(callback: (callState: StateType) => void): void {
this.callbacks.onCallDisconnected = callback;
}
public registerOnAdapterReady(callback: () => void): void {
this.callbacks.onAdapterReady = callback;
}
public registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean, reason: string) => void): void {
this.callbacks.onAgentAvailabilityChange = callback;
}
public registerOnForcedLogoutListener(callback: () => void): void {
this.callbacks.onForcedLogout = callback;
}
public registerOnLoginFailedListener(callback: () => void): void {
this.callbacks.onLoginFailed = callback;
}
public registerOnCallTransferStatus(callback: (data: GenericObject) => void): void {
this.callbacks.onCallTransferStatus = callback;
}
public async checkAllPermissions(): Promise<boolean> {
return await checkAllPermissions();
}
}
export default AmeyoAdapter;