Merge pull request #32 from navi-commons/NTP-26125
NTP-26125 | Call State Manager
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ output.html
|
||||
packages/*/dist
|
||||
.idea
|
||||
lerna-debug.log
|
||||
.Ds_Store
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## 1.1.15 (2025-01-16)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/adapter-ameyo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 1.0.100 (2025-01-06)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/adapter-ameyo
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-nocheck
|
||||
|
||||
|
||||
import messagingType from "../../../types/MessagingType.ts";
|
||||
import {parse} from "path";
|
||||
import {callStateManager} from "../../callStateManager.ts";
|
||||
import {EventData} from "../../types.ts";
|
||||
|
||||
export enum MessagingType {
|
||||
ON_AMEYO_CALL_INCOMING = 'onAmeyoCallIncoming',
|
||||
@@ -42,157 +41,6 @@ const monitorPushTime = 60 * 1000;
|
||||
//call the above function to create the XMLHttpRequest object
|
||||
let http = createRequestObject();
|
||||
|
||||
enum pushResponseTypes {
|
||||
UserCallModelUpdatedPush = 'UserCallModelUpdatedPush',
|
||||
CustomerCallMemberUpdatedPush = 'CustomerCallMemberUpdatedPush',
|
||||
UserCallMemberUpdatedPush = 'UserCallMemberUpdatedPush',
|
||||
CRMCreateNotifyPush = 'CRMCreateNotifyPush',
|
||||
UserCCRuntimeUpdatedPush = 'UserCCRuntimeUpdatedPush',
|
||||
UserLoggedOffPush = 'UserLoggedOffPush'
|
||||
}
|
||||
|
||||
export function parseQuerystring(url: string): Record<string, any> {
|
||||
const foo = url?.split('?')?.[1]?.split('&') || [];
|
||||
const dict = {};
|
||||
let elem = [];
|
||||
for (let i = foo.length - 1; i >= 0; i--) {
|
||||
elem = foo[i].split('=');
|
||||
dict[elem[0]] = elem[1];
|
||||
}
|
||||
return dict || {};
|
||||
}
|
||||
|
||||
function extractUserCallModelUpdatedPush(rawResponse) {
|
||||
const parts = rawResponse
|
||||
.replaceAll(`Content-type: application/json ; charset: utf-8\r\n\r\n`, '')
|
||||
.split("--DON'T_PANIC!_more_will_follow\r\n")
|
||||
.map((part) => {
|
||||
try {
|
||||
return JSON.parse(part.trim());
|
||||
} catch {
|
||||
return null; // Ignore parsing errors
|
||||
}
|
||||
})
|
||||
.filter(Boolean); // Remove null values
|
||||
|
||||
// Prioritize CRMCreateNotifyPush and keep the rest
|
||||
const sortedParts = [
|
||||
...parts.filter(part => part?.pushType === pushResponseTypes.CRMCreateNotifyPush),
|
||||
...parts.filter(part => part?.pushType !== pushResponseTypes.CRMCreateNotifyPush),
|
||||
];
|
||||
|
||||
sortedParts.forEach(jsonData => {
|
||||
if (jsonData?.pushType === pushResponseTypes.CRMCreateNotifyPush) {
|
||||
const crtObjectId = jsonData?.data?.crtObjectId;
|
||||
const parsedObject = parseQuerystring(jsonData?.data?.crmURL);
|
||||
let userCRTObjectId = parsedObject?.userCrtObjectId?.replace('%40', '@');
|
||||
userCRTObjectId = userCRTObjectId.replace('%40', '@');
|
||||
const phoneNumber = parsedObject?.phone;
|
||||
const lan = parsedObject?.loanaccountnumber;
|
||||
const callId = parsedObject?.unique_id || parsedObject?.callid;
|
||||
localStorage.setItem(
|
||||
'revEngCustomerInfo',
|
||||
JSON.stringify({phoneNumber, lan, crtObjectId, userCRTObjectId, callId})
|
||||
);
|
||||
}
|
||||
if (jsonData?.pushType === pushResponseTypes.UserCallModelUpdatedPush) {
|
||||
sendCallStatusMessage(jsonData);
|
||||
}
|
||||
if (jsonData?.pushType === pushResponseTypes.CustomerCallMemberUpdatedPush) {
|
||||
const payload = jsonData?.data;
|
||||
// handle for transfer call
|
||||
if (payload?.isDisposing &&
|
||||
payload?.status === 'hungup')
|
||||
sendCallStatusMessage(jsonData);
|
||||
}
|
||||
if (jsonData?.pushType === pushResponseTypes.UserCallMemberUpdatedPush) {
|
||||
const payload = jsonData?.data;
|
||||
const validStatuses = ["ringing", "connected", "hungup"];
|
||||
// handle for transfer call
|
||||
if (payload?.associationType === 'transfer.association' && validStatuses.includes(payload?.status)) {
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_CALL_TRANSFER,
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
}
|
||||
if (jsonData?.pushType === pushResponseTypes.UserCCRuntimeUpdatedPush) {
|
||||
const payload = jsonData?.data;
|
||||
//handle ameyo availablility changes
|
||||
window.postMessage({
|
||||
type: messagingType.ON_AMEYO_AVAILABILITY_CHANGE,
|
||||
data:{
|
||||
isOnBreak: payload?.isOnBreak,
|
||||
reason: payload?.statusDescription
|
||||
}
|
||||
})
|
||||
}
|
||||
if (jsonData?.pushType == pushResponseTypes.UserLoggedOffPush) {
|
||||
const payload = jsonData?.data;
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_FORCED_LOGOUT,
|
||||
data: {}
|
||||
})
|
||||
}
|
||||
} )
|
||||
}
|
||||
|
||||
const sendCallStatusMessage = (res) => {
|
||||
try {
|
||||
const status = res?.data?.status;
|
||||
|
||||
const getCustomerInfo = () =>
|
||||
JSON.parse(localStorage.getItem('revEngCustomerInfo') || '{}');
|
||||
|
||||
const sendConnectedMessage = (retryCount = 3) => {
|
||||
/* The callId is received after the call is connected.
|
||||
* Since callId is essential and cannot be null,
|
||||
* this retry mechanism ensures we wait until it becomes available. */
|
||||
const data = getCustomerInfo();
|
||||
if (data?.callId || data?.phoneNumber) {
|
||||
const message = {
|
||||
type: MessagingType.ON_AMEYO_CALL_ACCEPTED,
|
||||
data,
|
||||
};
|
||||
window.postMessage(message);
|
||||
} else if (retryCount > 0) {
|
||||
setTimeout(() => sendConnectedMessage(retryCount - 1), 100);
|
||||
} else {
|
||||
console.warn('Unable to send ON_AMEYO_CALL_ACCEPTED: callId not found.');
|
||||
}
|
||||
};
|
||||
|
||||
switch (status) {
|
||||
case 'ringing': {
|
||||
const data = getCustomerInfo();
|
||||
const message = {
|
||||
type: MessagingType.ON_AMEYO_CALL_INCOMING,
|
||||
data,
|
||||
};
|
||||
window.postMessage(message);
|
||||
break;
|
||||
}
|
||||
case 'connected': {
|
||||
sendConnectedMessage();
|
||||
break;
|
||||
}
|
||||
case 'hungup': {
|
||||
const data = getCustomerInfo();
|
||||
const message = {
|
||||
type: MessagingType.ON_AMEYO_CALL_DISCONNECTED,
|
||||
data,
|
||||
};
|
||||
window.postMessage(message);
|
||||
localStorage.removeItem('revEngCustomerInfo');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn(`Unhandled call status: ${status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in sendCallStatusMessage:', error);
|
||||
}
|
||||
};
|
||||
|
||||
function createRequestObject() {
|
||||
let tmpXmlHttpObject;
|
||||
@@ -262,9 +110,30 @@ function processResponse() {
|
||||
// response
|
||||
}
|
||||
|
||||
function extractAndProcessEvents(rawResponse: any) {
|
||||
const parts = rawResponse
|
||||
.replaceAll(`Content-type: application/json ; charset: utf-8\r\n\r\n`, '')
|
||||
.split("--DON'T_PANIC!_more_will_follow\r\n")
|
||||
.map((part: string) => {
|
||||
try {
|
||||
return JSON.parse(part.trim());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
parts.forEach((event : EventData) => {
|
||||
try {
|
||||
callStateManager.handleEvent(event);
|
||||
} catch (error) {
|
||||
console.error('Error processing event:', event, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processPush(pushResponse) {
|
||||
extractUserCallModelUpdatedPush(pushResponse);
|
||||
//ajaxRequest.handleIntermediateResponse(pushResponse);
|
||||
extractAndProcessEvents(pushResponse);
|
||||
}
|
||||
|
||||
function makePostRequest(url, session) {
|
||||
|
||||
189
packages/adapter-ameyo/lib/callStateManager.ts
Normal file
189
packages/adapter-ameyo/lib/callStateManager.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import {MessagingType} from "./assets/js/ajaxClient.ts";
|
||||
import {CallStatus, PushType, CallState} from "./types.ts";
|
||||
import {parseQuerystring} from "@universal-call-sdk/common/lib/utils/parsingUtils.ts";
|
||||
import messagingType from "../types/MessagingType.ts";
|
||||
|
||||
class CallStateManager {
|
||||
private state: CallState = {
|
||||
isConnected: false,
|
||||
lastCallStatus: 'inactive',
|
||||
customerCallStatus: null,
|
||||
crtObjectId: null,
|
||||
connectionAttempts: 0,
|
||||
customerInfo: null
|
||||
};
|
||||
|
||||
private readonly MAX_CONNECTION_ATTEMPTS = 3;
|
||||
private connectionTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
public sendConnectedMessage() {
|
||||
const customerInfo = this.state.customerInfo;
|
||||
|
||||
if (customerInfo?.callId || customerInfo?.phoneNumber) {
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_CALL_ACCEPTED,
|
||||
data: customerInfo
|
||||
});
|
||||
this.state.connectionAttempts = 0;
|
||||
this.clearConnectionTimeout();
|
||||
} else if (this.state.connectionAttempts < this.MAX_CONNECTION_ATTEMPTS) {
|
||||
this.state.connectionAttempts++;
|
||||
this.setConnectionTimeout();
|
||||
} else {
|
||||
console.warn('Max connection attempts reached without valid customer info');
|
||||
this.clearConnectionTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
private setConnectionTimeout() {
|
||||
this.clearConnectionTimeout();
|
||||
this.connectionTimeout = setTimeout(() => {
|
||||
this.sendConnectedMessage();
|
||||
}, 100 * Math.pow(2, this.state.connectionAttempts)); // Exponential backoff
|
||||
}
|
||||
|
||||
private clearConnectionTimeout() {
|
||||
if (this.connectionTimeout) {
|
||||
clearTimeout(this.connectionTimeout);
|
||||
this.connectionTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
public handleEvent(jsonData: any) {
|
||||
const pushType = jsonData?.pushType;
|
||||
const data = jsonData?.data;
|
||||
|
||||
switch (pushType) {
|
||||
case PushType.UserCallModelUpdated:
|
||||
this.handleCallModelUpdate(data);
|
||||
break;
|
||||
|
||||
case PushType.CustomerCallMemberUpdated:
|
||||
this.handleCustomerCallUpdate(data);
|
||||
break;
|
||||
|
||||
case PushType.CustomerCallMemberCreated:
|
||||
this.handleCustomerCallCreated(data);
|
||||
break;
|
||||
|
||||
case PushType.CRMCreateNotify:
|
||||
this.handleCRMCreate(data);
|
||||
break;
|
||||
|
||||
case PushType.UserCallMemberUpdated:
|
||||
this.handleUserCallMemberUpdate(data);
|
||||
break;
|
||||
|
||||
case PushType.UserCCRuntimeUpdated:
|
||||
this.handleUserCCRuntimeUpdate(data);
|
||||
break;
|
||||
|
||||
case PushType.UserLoggedOff:
|
||||
this.handleUserLoggedOff();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private handleCallModelUpdate(data: any) {
|
||||
const status = data?.status as CallStatus;
|
||||
this.state.lastCallStatus = status;
|
||||
|
||||
switch (status) {
|
||||
case 'ringing':
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_CALL_INCOMING,
|
||||
data: this.state.customerInfo
|
||||
});
|
||||
break;
|
||||
case 'hungup':
|
||||
this.handleCallDisconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private handleCustomerCallUpdate(data: any) {
|
||||
this.state.customerCallStatus = data?.status;
|
||||
this.state.crtObjectId = data?.crtObjectId;
|
||||
|
||||
// handle for transfer call
|
||||
if (data?.isDisposing && data?.status === 'hungup') {
|
||||
this.handleCallDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private handleCustomerCallCreated(data: any) {
|
||||
this.state.customerInfo = {
|
||||
...this.state.customerInfo,
|
||||
phoneNumber: data?.phone,
|
||||
crtObjectId: data?.crtObjectId,
|
||||
callId: data?.callId
|
||||
};
|
||||
}
|
||||
|
||||
private handleCRMCreate(data: any) {
|
||||
const crmURL = data?.crmURL;
|
||||
if (!crmURL) return;
|
||||
|
||||
const parsedQuery = parseQuerystring(crmURL);
|
||||
const userCRTObjectId = parsedQuery?.userCrtObjectId?.replaceAll('%40', '@');
|
||||
|
||||
this.state.customerInfo = {
|
||||
...this.state.customerInfo,
|
||||
phoneNumber: parsedQuery?.phone,
|
||||
lan: parsedQuery?.loanaccountnumber,
|
||||
crtObjectId: data?.crtObjectId,
|
||||
userCRTObjectId,
|
||||
callId: parsedQuery?.unique_id || parsedQuery?.callid
|
||||
};
|
||||
}
|
||||
|
||||
private handleUserCallMemberUpdate(data: any) {
|
||||
const validStatuses = ["ringing", "connected", "hungup"];
|
||||
|
||||
if (data?.associationType === 'transfer.association' &&
|
||||
validStatuses.includes(data?.status)) {
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_CALL_TRANSFER,
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private handleUserCCRuntimeUpdate(data: any) {
|
||||
const payload = data;
|
||||
window.postMessage({
|
||||
type: messagingType.ON_AMEYO_AVAILABILITY_CHANGE,
|
||||
data:{
|
||||
isOnBreak: payload?.isOnBreak,
|
||||
reason: payload?.statusDescription
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private handleUserLoggedOff() {
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_FORCED_LOGOUT,
|
||||
data: {}
|
||||
});
|
||||
}
|
||||
|
||||
private handleCallDisconnect() {
|
||||
window.postMessage({
|
||||
type: MessagingType.ON_AMEYO_CALL_DISCONNECTED,
|
||||
data: this.state.customerInfo
|
||||
});
|
||||
|
||||
// Reset state
|
||||
this.state = {
|
||||
isConnected: false,
|
||||
lastCallStatus: 'inactive',
|
||||
customerCallStatus: null,
|
||||
crtObjectId: null,
|
||||
connectionAttempts: 0,
|
||||
customerInfo: null
|
||||
};
|
||||
this.clearConnectionTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
export const callStateManager = new CallStateManager();
|
||||
156
packages/adapter-ameyo/lib/htmlTagManager.ts
Normal file
156
packages/adapter-ameyo/lib/htmlTagManager.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
type TagConfig = {
|
||||
id: string;
|
||||
type: 'audio' | 'video';
|
||||
attributes?: Record<string, string | boolean>;
|
||||
styles?: Partial<CSSStyleDeclaration>;
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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<void> {
|
||||
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();
|
||||
@@ -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, logoutFromAmeyo
|
||||
} from "./api.ts";
|
||||
getCampaignId,
|
||||
ameyoDisposeCall,
|
||||
getAllAgentsForTransferCall,
|
||||
transferCallToAgent, logoutFromAmeyo
|
||||
} 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, reason: string) => 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,384 +82,369 @@ 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<void> => {
|
||||
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_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 async handleCorsBypassResponse(payload: GenericObject): Promise<void> {
|
||||
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);
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (payload?.data?.requestKey === RequestKeys.AMEYO_ON_BREAK) {
|
||||
setAutoStatus(this.sessionId);
|
||||
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<void> {
|
||||
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_AVAILABILITY_CHANGE) {
|
||||
this.isAgentAvailable = !data?.data?.isOnBreak;
|
||||
this.callbacks.onAgentAvailabilityChange(this.isAgentAvailable, data?.data?.reason || '');
|
||||
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: this.isAgentAvailable,});
|
||||
|
||||
}
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean, reason: string) => void) {
|
||||
console.log('registerOnAgentAvailabilityChange');
|
||||
this.callbacks.onAgentAvailabilityChange = callback;
|
||||
private handleAgentBreak(): void {
|
||||
setAutoStatus(this.sessionId);
|
||||
}
|
||||
|
||||
registerOnForcedLogoutListener(callback: () => void) {
|
||||
console.log('registerOnAgentAvailabilityChange');
|
||||
this.callbacks.onForcedLogout = callback;
|
||||
private async handleOmniQueueService(): Promise<void> {
|
||||
await selectCampaign(this.sessionId, this.userName.toLowerCase(), this.campaignId);
|
||||
}
|
||||
|
||||
registerOnLoginFailedListener(callback: ()=>void) {
|
||||
console.log('register on login failed');
|
||||
this.callbacks.onLoginFailed = callback
|
||||
private async handleCampaignSelection(): Promise<void> {
|
||||
await autoSelectExtension(this.sessionId, this.userName.toLowerCase());
|
||||
this.callbacks.onAdapterReady();
|
||||
this.currentCallState = CALL_STATES.IDLE;
|
||||
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false});
|
||||
}
|
||||
|
||||
registerOnAgentsForCallTransfer(callback: (data: GenericObject) => void) {
|
||||
console.log('registerOnAgentsForCallTransfer');
|
||||
this.callbacks.onAgentsForCallTransfer = callback;
|
||||
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,});
|
||||
}
|
||||
|
||||
registerOnCallTransferStatus(callback: (data: GenericObject) => void) {
|
||||
this.callbacks.onCallTransferStatus = callback;
|
||||
private handleAgentsForTransfer(payload: GenericObject): void {
|
||||
this.callbacks.onAgentsForCallTransfer(payload?.data?.response);
|
||||
}
|
||||
|
||||
acceptCall() {
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
logOut() {
|
||||
logoutFromAmeyo(this.sessionId);
|
||||
// Event registration methods
|
||||
public registerOnCallIncoming(callback: (callState: StateType) => void): void {
|
||||
this.callbacks.onCallIncoming = callback;
|
||||
}
|
||||
|
||||
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<CSSStyleDeclaration>;
|
||||
[key: string]: any;
|
||||
};
|
||||
public registerOnCallConnected(callback: (callState: StateType) => void): void {
|
||||
this.callbacks.onCallConnected = callback;
|
||||
}
|
||||
|
||||
function createElement(
|
||||
tag: keyof HTMLElementTagNameMap,
|
||||
attributes: ElementAttributes = {},
|
||||
parent: HTMLElement = document.body
|
||||
): HTMLElement {
|
||||
const element = document.createElement(tag);
|
||||
public registerOnCallDisconnected(callback: (callState: StateType) => void): void {
|
||||
this.callbacks.onCallDisconnected = 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 registerOnAdapterReady(callback: () => void): void {
|
||||
this.callbacks.onAdapterReady = callback;
|
||||
}
|
||||
|
||||
parent.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
public registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean, reason: string) => void): void {
|
||||
this.callbacks.onAgentAvailabilityChange = callback;
|
||||
}
|
||||
|
||||
createElement('audio', {
|
||||
id: 'audio_remote',
|
||||
autoplay: true,
|
||||
});
|
||||
public registerOnForcedLogoutListener(callback: () => void): void {
|
||||
this.callbacks.onForcedLogout = 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',
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
@@ -65,6 +65,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, reason: string) => void;
|
||||
onForcedLogout: () => void;
|
||||
onLoginFailed: (err: GenericObject) => void;
|
||||
onAgentsForCallTransfer: (data: GenericObject) => void;
|
||||
onCallTransferStatus: (data: GenericObject) => void;
|
||||
};
|
||||
|
||||
|
||||
export enum CALL_STATES {
|
||||
CALL_INCOMING = 'CALL_INCOMING',
|
||||
@@ -77,3 +89,35 @@ export type CallTransferData = {
|
||||
campaignId : string,
|
||||
targetCRTObjectId: string
|
||||
}
|
||||
|
||||
export type CallStatus = 'inactive' | 'initialized' | 'ringing' | 'connected' | 'hungup';
|
||||
|
||||
export enum PushType {
|
||||
UserCallModelUpdated = 'UserCallModelUpdatedPush',
|
||||
CustomerCallMemberUpdated = 'CustomerCallMemberUpdatedPush',
|
||||
CustomerCallMemberCreated = 'CustomerCallMemberCreatedPush',
|
||||
CRMCreateNotify = 'CRMCreateNotifyPush',
|
||||
UserCallMemberUpdated = 'UserCallMemberUpdatedPush',
|
||||
UserCCRuntimeUpdated = 'UserCCRuntimeUpdatedPush',
|
||||
UserLoggedOff = 'UserLoggedOffPush'
|
||||
}
|
||||
|
||||
export interface CallState {
|
||||
isConnected: boolean;
|
||||
lastCallStatus: CallStatus;
|
||||
customerCallStatus: string | null;
|
||||
crtObjectId: string | null;
|
||||
connectionAttempts: number;
|
||||
customerInfo: {
|
||||
phoneNumber?: string;
|
||||
lan?: string;
|
||||
crtObjectId?: string;
|
||||
userCRTObjectId?: string;
|
||||
callId?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface EventData {
|
||||
pushType: PushType;
|
||||
data: any;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@universal-call-sdk/adapter-ameyo",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -9,7 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@universal-call-sdk/common": "^1.0.54",
|
||||
"@universal-call-sdk/common": "^1.1.13",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## 1.1.13 (2025-01-16)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/common
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 1.0.51 (2025-01-06)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/common
|
||||
|
||||
10
packages/common/lib/utils/parsingUtils.ts
Normal file
10
packages/common/lib/utils/parsingUtils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function parseQuerystring(url: string): Record<string, any> {
|
||||
const foo = url?.split('?')?.[1]?.split('&') || [];
|
||||
const dict: Record<string, any> = {};
|
||||
let elem = [];
|
||||
for (let i = foo.length - 1; i >= 0; i--) {
|
||||
elem = foo[i].split('=');
|
||||
dict[elem[0]] = elem[1];
|
||||
}
|
||||
return dict || {};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@universal-call-sdk/common",
|
||||
"version": "1.0.54",
|
||||
"version": "1.1.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## 1.1.12 (2025-01-16)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 1.0.55 (2025-01-06)
|
||||
|
||||
**Note:** Version bump only for package @universal-call-sdk/core
|
||||
|
||||
@@ -91,7 +91,7 @@ function UseCallSdk({AdapterClass, adapterOptions, metricsConfig, clickStreamCon
|
||||
}) {
|
||||
useEffect(() => {
|
||||
adapter = new AdapterClass(adapterOptions);
|
||||
}, []);
|
||||
}, [adapterOptions]);
|
||||
// @ts-expect-error sdfsf
|
||||
const [callState] = useReducer<any>(reducer, initialState,()=> initialState);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@universal-call-sdk/core",
|
||||
"version": "1.0.58",
|
||||
"version": "1.1.12",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Reference in New Issue
Block a user