Files
call-sdk/packages/adapter-ameyo/lib/callStateManager.ts
2025-01-13 02:01:01 +05:30

231 lines
6.9 KiB
TypeScript

import {MessagingType, parseQuerystring} from "./assets/js/ajaxClient.ts";
import {CallStatus, PushType, CallState} from "./types.ts";
interface EventData {
pushType: PushType;
data: any; // We can make this more specific if needed
}
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;
private 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 'connected':
if (!this.state.isConnected) {
this.state.isConnected = true;
this.sendConnectedMessage();
}
break;
case 'hungup':
this.handleCallDisconnect();
break;
}
}
private handleCustomerCallUpdate(data: any) {
this.state.customerCallStatus = data?.status;
this.state.crtObjectId = data?.crtObjectId;
// Keep disconnect handling
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) {
if (data?.isOnBreak) {
window.postMessage({
type: MessagingType.ON_AMEYO_AGENT_ON_BREAK,
data: {
reason: data
}
});
}
}
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();
export 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);
// Process events in order, prioritizing CRMCreateNotifyPush
const sortedParts: EventData[] = [
...parts.filter((part: EventData | null): part is EventData =>
part?.pushType === PushType.CRMCreateNotify),
...parts.filter((part: EventData | null): part is EventData =>
part?.pushType !== PushType.CRMCreateNotify)
];
sortedParts.forEach(event => {
try {
callStateManager.handleEvent(event);
} catch (error) {
console.error('Error processing event:', event, error);
}
});
}