# Conflicts: # packages/adapter-ameyo/CHANGELOG.md # packages/common/CHANGELOG.md # packages/core/CHANGELOG.md
433 lines
15 KiB
TypeScript
433 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,
|
|
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 {callStateManager} from "./callStateManager.ts";
|
|
import MessagingType from "../types/MessagingType.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.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"
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
private handleMessage = async ({data}: MessageEvent): Promise<void> => {
|
|
try {
|
|
if (this.shouldTrackMetrics(data.type)) {
|
|
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('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 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
|
|
});
|
|
}
|
|
}
|
|
|
|
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();
|
|
callStateManager.sendConnectedMessage();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
export default AmeyoAdapter;
|