Merge pull request #6 from navi-commons/TP-89230/shaping-univeral-call-sdk

TP-89230 | shaping univeral call sdk
This commit is contained in:
Varnit Goyal
2024-11-25 16:05:55 +05:30
committed by GitHub
16 changed files with 203 additions and 112 deletions

3
.gitignore vendored
View File

@@ -2,5 +2,6 @@ node_modules/
static/
output.json
output.html
packages/*/dist
.idea
lerna-debug.log
lerna-debug.log

1
.npmrc
View File

@@ -0,0 +1 @@
registry=http://localhost:4873/

View File

@@ -159,6 +159,28 @@ export const ameyoHangupUser = (sessionId: string, userCRTObjectId: string) => {
});
};
export const ameyoDisposeCall = (sessionId: string, campaignId : string,crtObjectId: string, userCRTObjectId: string) => {
return getResponseWithoutCors({
url: `${window.BASE_AMEYO_URL}/ameyorestapi/voice/disposeCall`,
method: 'POST',
requestKey: RequestKeys.HANGUP_USER,
requestType: RequestType.JSON,
data: {
sessionId,
campaignId,
crtObjectId,
userCRTObjectId,
"dispositionCodeId": 21,
"requestId": "d0045ff7-8419-0860-8621-2c67ad4eb02f",
"dispositionParams": {},
"notes": ""
},
headers: {
sessionId
}
});
};
export const getCampaignId = (sessionId: string) => {
return getResponseWithoutCors({
url: `${window.BASE_AMEYO_URL}/ameyorestapi/sessionData/getAllSessionData`,

View File

@@ -3,9 +3,10 @@
import messagingType from "../../../types/MessagingType.ts";
import {parse} from "path";
export enum MessagingType {
ON_AMEYO_CALL_INCOMING= 'onAmeyoCallIncoming',
export enum MessagingType {
ON_AMEYO_CALL_INCOMING = 'onAmeyoCallIncoming',
ON_AMEYO_CALL_ACCEPTED = 'onAmeyoCallAccepted',
ON_AMEYO_CALL_DISCONNECTED = 'onAmeyoCallDisconnected',
ON_AMEYO_AGENT_ON_BREAK = 'onAmeyoAgentOnBreak',
@@ -43,8 +44,8 @@ let http = createRequestObject();
enum pushResponseTypes {
UserCallModelUpdatedPush = 'UserCallModelUpdatedPush',
CRMCreateNotifyPush = 'CRMCreateNotifyPush',
UserCCRuntimeUpdatedPush= 'UserCCRuntimeUpdatedPush',
UserLoggedOffPush= 'UserLoggedOffPush'
UserCCRuntimeUpdatedPush = 'UserCCRuntimeUpdatedPush',
UserLoggedOffPush = 'UserLoggedOffPush'
}
export function parseQuerystring(url: string): Record<string, any> {
@@ -69,11 +70,14 @@ function extractUserCallModelUpdatedPush(rawResponse) {
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;
localStorage.setItem(
'revEngCustomerInfo',
JSON.stringify({ phoneNumber, lan, crtObjectId })
JSON.stringify({phoneNumber, lan, crtObjectId, userCRTObjectId, callId})
);
sendCallStatusMessage(res);
}
@@ -81,8 +85,8 @@ function extractUserCallModelUpdatedPush(rawResponse) {
res = jsonData;
sendCallStatusMessage(res);
}
if(jsonData.pushType === pushResponseTypes.UserCCRuntimeUpdatedPush) {
const payload= jsonData?.data;
if (jsonData.pushType === pushResponseTypes.UserCCRuntimeUpdatedPush) {
const payload = jsonData?.data;
//handle ameyo erroronous condition
if (payload?.isOnBreak) {
window.postMessage({
@@ -93,7 +97,7 @@ function extractUserCallModelUpdatedPush(rawResponse) {
});
}
}
if(jsonData?.pushType == pushResponseTypes.UserLoggedOffPush) {
if (jsonData?.pushType == pushResponseTypes.UserLoggedOffPush) {
const payload = jsonData?.data;
window.postMessage({
type: MessagingType.ON_AMEYO_FORCED_LOGOUT,
@@ -108,35 +112,23 @@ function extractUserCallModelUpdatedPush(rawResponse) {
const sendCallStatusMessage = res => {
let message = null;
const { phoneNumber, lan, crtObjectId } = JSON.parse(
const data = JSON.parse(
localStorage.getItem('revEngCustomerInfo') || '{}'
);
if (res?.data?.status === 'ringing') {
message = {
type: MessagingType.ON_AMEYO_CALL_INCOMING,
data: {
phoneNumber,
lan,
crtObjectId
}
data
};
} else if (res?.data?.status === 'connected') {
message = {
type: MessagingType.ON_AMEYO_CALL_ACCEPTED,
data: {
phoneNumber,
lan,
crtObjectId
}
data
};
} else if (res?.data?.status === 'hungup') {
message = {
type: MessagingType.ON_AMEYO_CALL_DISCONNECTED,
data: {
phoneNumber,
lan,
crtObjectId
}
data
};
}
if (message) window.postMessage(message);
@@ -210,9 +202,9 @@ function processResponse() {
// response
}
function processPush(pushResponse) {
function processPush(pushResponse) {
console.log('push response', pushResponse);
extractUserCallModelUpdatedPush(pushResponse);
extractUserCallModelUpdatedPush(pushResponse);
//ajaxRequest.handleIntermediateResponse(pushResponse);
}

View File

@@ -11,7 +11,7 @@ import {
selectCampaign,
setAgentActive, setAgentOnBreak,
setAutoStatus,
getCampaignId
getCampaignId, ameyoDisposeCall
} from "./api.ts";
import {
acceptSipCall,
@@ -28,9 +28,9 @@ class AmeyoAdapter implements IAdapter {
onCallConnected: (data: StateType) => void;
onCallDisconnected: (data: StateType) => void;
onCallIncoming: (data: StateType) => void,
onAdapterReady: ()=> void,
onAdapterReady: () => void,
onAgentAvailabilityChange: (isAgentAvailable: boolean) => void
onForcedLogout: ()=>void
onForcedLogout: () => void
};
private eventListenerUrl: string;
private baseUrl: string;
@@ -42,33 +42,41 @@ class AmeyoAdapter implements IAdapter {
private sipAccountInfo: GenericObject;
private isAgentAvailable: boolean;
constructor(options : AmeyoInitializationOptions) {
constructor(options: AmeyoInitializationOptions) {
console.log('AmeyoAdapter constructor');
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
this._appendTags();
script.async = true;
document.body.appendChild(script);
this.baseUrl = options.baseUrl;
this.eventListenerUrl = options.eventListenerUrl;
this.callbacks = {
onCallIncoming: ()=> {},
onCallConnected: ()=> {},
onCallDisconnected: ()=> {},
onAdapterReady: ()=> {},
onAgentAvailabilityChange: ()=> {},
onForcedLogout: ()=> {}
onCallIncoming: () => {
},
onCallConnected: () => {
},
onCallDisconnected: () => {
},
onAdapterReady: () => {
},
onAgentAvailabilityChange: () => {
},
onForcedLogout: () => {
}
};
this.sessionId = '';
this.userName = options.userName;
this.password = options.password;
this.campaignId = '';
this.sipAccountInfo = {};
this.currentCallMetadata= {};
this.isAgentAvailable= false;
this.currentCallMetadata = {};
this.isAgentAvailable = false;
window.BASE_AMEYO_URL = this.baseUrl;
window.AMEYO_LOGIN_URL = options.loginUrl
}
init() {
console.log('initializing ameyo adapter');
window.addEventListener('message', this._registerMessageListener);
@@ -77,7 +85,7 @@ class AmeyoAdapter implements IAdapter {
}
_initializeSipStack = ({accountName, userName, domain, password}: SipAccountInfo) => {
_initializeSipStack = ({accountName, userName, domain, password}: SipAccountInfo) => {
console.log('initializing sip stack');
const domainOnly = domain.split(':')[0];
const port = domain.split(':')[1];
@@ -89,7 +97,7 @@ class AmeyoAdapter implements IAdapter {
}
_initializeAmeyo = () => {
loginInAmeyo(this.userName?.toLowerCase(), this.password);
loginInAmeyo(this.userName?.toLowerCase(), this.password);
}
_onListenForCorsBypassResponse = (payload: GenericObject) => {
@@ -104,7 +112,7 @@ class AmeyoAdapter implements IAdapter {
if (payload?.data?.requestKey === RequestKeys.AMEYO_HEARTBEAT) {
console.log('heartbeat response', payload?.data?.response);
}
if(payload?.data?.requestKey === RequestKeys.SIP_ACCOUNT_INFO) {
if (payload?.data?.requestKey === RequestKeys.SIP_ACCOUNT_INFO) {
console.log('sip account info', payload?.data?.response);
const response = payload?.data?.response;
this._initializeSipStack({
@@ -118,74 +126,77 @@ class AmeyoAdapter implements IAdapter {
getCampaignId(this.sessionId);
setAutoStatus(this.sessionId);
}
if(payload?.data?.requestKey === RequestKeys.GET_CAMPAIGN_ID) {
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 });
if (payload?.data?.requestKey === RequestKeys.AMEYO_AVAILABLE) {
setAutoStatus(this.sessionId);
this.isAgentAvailable = true;
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: true});
}
if (payload?.data?.requestKey === RequestKeys.OMNIQUEUE_SERVICE) {
selectCampaign(this.sessionId, this.userName.toLowerCase(), this.campaignId);
}
if(payload?.data?.requestKey === RequestKeys.SELECT_CAMPAIGN) {
if (payload?.data?.requestKey === RequestKeys.SELECT_CAMPAIGN) {
console.log('campaign selected', payload?.data?.response);
autoSelectExtension(this.sessionId, this.userName.toLowerCase());
this.callbacks.onAdapterReady();
this.callbacks.onAgentAvailabilityChange(true);
window.postMessage({ type: 'onAmeyoAvailabiltyChange', data: false }, );
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false},);
}
if(payload?.data?.requestKey === RequestKeys.AMEYO_ON_BREAK) {
if (payload?.data?.requestKey === RequestKeys.AMEYO_ON_BREAK) {
setAutoStatus(this.sessionId);
this.isAgentAvailable = false;
this.callbacks.onAgentAvailabilityChange(false);
window.postMessage({ type: 'onAmeyoAvailabiltyChange', data: false }, );
window.postMessage({type: 'onAmeyoAvailabiltyChange', data: false},);
}
}
_registerMessageListener = async ({ data }: GenericObject) => {
_registerMessageListener = async ({data}: GenericObject) => {
if (data?.type === MessagingType.SET_RESPONSE_WITHOUT_CORS) {
this._onListenForCorsBypassResponse(data);
}
if(data?.type === MessagingType.ON_AMEYO_CALL_INCOMING) {
if (data?.type === MessagingType.ON_AMEYO_CALL_INCOMING) {
this.callbacks.onCallIncoming(data?.data);
this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}
}
if(data?.type === MessagingType.ON_AMEYO_CALL_ACCEPTED) {
if (data?.type === MessagingType.ON_AMEYO_CALL_ACCEPTED) {
this.callbacks.onCallConnected(data?.data);
this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}
}
if(data?.type === MessagingType.ON_AMEYO_CALL_DISCONNECTED) {
if (data?.type === MessagingType.ON_AMEYO_CALL_DISCONNECTED) {
this.callbacks.onCallDisconnected(data?.data);
ameyoHangupUser(this.sessionId, this.currentCallMetadata?.crtObjectId);
ameyoHangupUser(this.sessionId, this.currentCallMetadata?.userCRTObjectId);
this.currentCallMetadata = {...this.currentCallMetadata, ...data?.data}
}
if(data?.type === MessagingType.ON_AMEYO_AGENT_ON_BREAK) {
console.log('on availability changed')
if (data?.type === MessagingType.ON_AMEYO_AGENT_ON_BREAK) {
console.log('on availability changedd')
this.isAgentAvailable = false;
this.callbacks.onAgentAvailabilityChange(false);
}
if(data?.type === MessagingType.ON_AMEYO_FORCED_LOGOUT){
if (data?.type === MessagingType.ON_AMEYO_FORCED_LOGOUT) {
this.callbacks.onForcedLogout()
}
};
registerOnCallIncoming(callback: (callState: StateType)=> void) {
registerOnCallIncoming(callback: (callState: StateType) => void) {
console.log('registerOnCallIncoming');
this.callbacks.onCallIncoming = callback;
}
registerOnCallConnected(callback: (callState: StateType)=> void) {
registerOnCallConnected(callback: (callState: StateType) => void) {
console.log('registerOnCallConnected');
this.callbacks.onCallConnected = callback;
}
registerOnCallDisconnected(callback: (callState: StateType)=> void) {
registerOnCallDisconnected(callback: (callState: StateType) => void) {
console.log('registerOnCallDisconnected');
this.callbacks.onCallDisconnected = callback;
}
registerOnAdapterReady(callback: ()=> void) {
registerOnAdapterReady(callback: () => void) {
console.log('registerOnAdapterReady');
this.callbacks.onAdapterReady = callback;
}
@@ -204,28 +215,36 @@ class AmeyoAdapter implements IAdapter {
console.log('acceptCall');
acceptSipCall();
}
rejectCall() {
console.log('rejectCall');
sipHangUp();
}
disposeCall() {
console.log('disposeCall');
ameyoDisposeCall(this.sessionId, this.campaignId, this.currentCallMetadata?.crtObjectId, this.currentCallMetadata?.userCRTObjectId);
}
setOnBreak() {
console.log('setAgentOnBreakk');
setAgentOnBreak(this.sessionId);
}
setAvailable() {
setAgentActive(this.sessionId);
setAgentActive(this.sessionId);
}
muteCall() {
console.log('muteCall');
sipMuteCall();
}
unmuteCall() {
console.log('unmuteCall');
sipUnmuteCall();
}
getAgentAvailability() {
return this.isAgentAvailable;
}
@@ -234,6 +253,97 @@ class AmeyoAdapter implements IAdapter {
return this.currentCallMetadata;
}
private _appendTags: () => void = () => {
type ElementAttributes = {
id?: string;
className?: string;
width?: string;
height?: string;
autoplay?: boolean;
muted?: boolean;
loop?: boolean;
src?: string;
style?: Partial<CSSStyleDeclaration>;
[key: string]: any;
};
function createElement(
tag: keyof HTMLElementTagNameMap,
attributes: ElementAttributes = {},
parent: HTMLElement = document.body
): HTMLElement {
const element = document.createElement(tag);
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]);
}
});
parent.appendChild(element);
return element;
}
createElement('audio', {
id: 'audio_remote',
autoplay: true,
});
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',
});
};
}
export default AmeyoAdapter;

View File

@@ -1,6 +1,6 @@
{
"name": "@universal-call-sdk/adapter-ameyo",
"version": "1.0.51",
"version": "1.0.59",
"type": "module",
"scripts": {
"dev": "vite",
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@universal-call-sdk/common": "^1.0.13"
"@universal-call-sdk/common": "^1.0.14"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
@@ -27,5 +27,5 @@
"vite": "^5.4.8",
"vite-plugin-dts": "^4.2.4"
},
"gitHead": "2b9cb67dbb3574023b6a93dc403b38aa82b58f51"
"gitHead": "2c345f2195eade5e14a659cff0bd5fa346e62e88"
}

View File

@@ -1,19 +0,0 @@
import { default as GenericObject } from '../types/GenericObject.ts';
declare class IAdapter {
registerOnCallIncoming(callback: (callState: GenericObject) => void): void;
registerOnCallConnected(callback: (callState: GenericObject) => void): void;
registerOnCallDisconnected(callback: (callState: GenericObject) => void): void;
registerOnAdapterReady(callback: () => void): void;
registerOnAgentAvailabilityChange(callback: (isAgentAvailable: boolean) => void): void;
registerOnForcedLogoutListener(callback: () => void): void;
acceptCall(): void;
rejectCall(): void;
muteCall(): void;
unmuteCall(): void;
setOnBreak(): void;
setAvailable(): void;
init(): void;
getAgentAvailability(): boolean;
getLatestCallState(): {};
}
export default IAdapter;

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,4 +0,0 @@
export type GenericObject = {
[key: string]: any;
};
export default GenericObject;

View File

@@ -1,5 +0,0 @@
declare enum RequestType {
JSON = "JSON",
RAW = "RAW"
}
export default RequestType;

View File

@@ -1,11 +0,0 @@
import { default as RequestType } from '../types/RequestType.ts';
type GetResponseWithoutCors = {
url: string;
method: string;
data?: Record<string, any> | string;
requestKey: string;
requestType?: RequestType;
headers?: Record<string, any> | null;
};
export declare const getResponseWithoutCors: ({ url, method, data, requestKey, requestType, headers }: GetResponseWithoutCors) => void;
export default getResponseWithoutCors;

View File

@@ -13,6 +13,7 @@ class IAdapter {
acceptCall() {}
rejectCall() {}
muteCall() {}
disposeCall() {}
unmuteCall() {}
setOnBreak() {}
@@ -27,4 +28,4 @@ class IAdapter {
}
export default IAdapter;
export default IAdapter;

View File

@@ -1,6 +1,6 @@
{
"name": "@universal-call-sdk/common",
"version": "1.0.13",
"version": "1.0.14",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -114,6 +114,10 @@ function UseCallSdk({AdapterClass, adapterOptions} : {AdapterClass: new (adapter
adapter.rejectCall();
}
function disposeCall() {
adapter.disposeCall();
}
function muteCall() {
adapter.muteCall();
}
@@ -152,6 +156,7 @@ function UseCallSdk({AdapterClass, adapterOptions} : {AdapterClass: new (adapter
registerOnForcedLogoutListener,
acceptCall,
rejectCall,
disposeCall,
muteCall,
unmuteCall,
initialize,
@@ -164,4 +169,4 @@ function UseCallSdk({AdapterClass, adapterOptions} : {AdapterClass: new (adapter
}
}
export default UseCallSdk;
export default UseCallSdk;

View File

@@ -1,6 +1,6 @@
{
"name": "@universal-call-sdk/core",
"version": "1.0.18",
"version": "1.0.19",
"type": "module",
"scripts": {
"dev": "vite",