mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: add Ozonetel Set Disposition API for proper ACW release
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,8 @@ export class OzonetelAgentService {
|
||||
private readonly apiDomain: string;
|
||||
private readonly apiKey: string;
|
||||
private readonly accountId: string;
|
||||
private cachedToken: string | null = null;
|
||||
private tokenExpiry: number = 0;
|
||||
|
||||
constructor(private config: ConfigService) {
|
||||
this.apiDomain = config.get<string>('exotel.subdomain') ?? 'in1-ccaas-api.ozonetel.com';
|
||||
@@ -15,6 +17,29 @@ export class OzonetelAgentService {
|
||||
this.accountId = config.get<string>('exotel.accountSid') ?? '';
|
||||
}
|
||||
|
||||
private async getToken(): Promise<string> {
|
||||
if (this.cachedToken && Date.now() < this.tokenExpiry) {
|
||||
return this.cachedToken;
|
||||
}
|
||||
|
||||
const url = `https://${this.apiDomain}/ca_apis/CAToken/generateToken`;
|
||||
this.logger.log('Generating CloudAgent API token');
|
||||
|
||||
const response = await axios.post(url, { userName: this.accountId }, {
|
||||
headers: { apiKey: this.apiKey, 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
if (data.token) {
|
||||
this.cachedToken = data.token;
|
||||
this.tokenExpiry = Date.now() + 55 * 60 * 1000;
|
||||
this.logger.log('CloudAgent token generated successfully');
|
||||
return data.token;
|
||||
}
|
||||
|
||||
throw new Error(data.message ?? 'Token generation failed');
|
||||
}
|
||||
|
||||
async loginAgent(params: {
|
||||
agentId: string;
|
||||
password: string;
|
||||
@@ -63,61 +88,107 @@ export class OzonetelAgentService {
|
||||
}
|
||||
}
|
||||
|
||||
async dialCustomer(params: {
|
||||
async manualDial(params: {
|
||||
agentId: string;
|
||||
campaignName: string;
|
||||
customerNumber: string;
|
||||
callbackUrl?: string;
|
||||
ivrUrl?: string;
|
||||
}): Promise<{ status: string; message: string }> {
|
||||
const callerId = process.env.OZONETEL_DID ?? '918041763265';
|
||||
const callbackBase = process.env.KOOKOO_CALLBACK_URL ?? 'https://engage-api.srv1477139.hstgr.cloud';
|
||||
const roomId = `room-${Date.now()}`;
|
||||
}): Promise<{ status: string; ucid?: string; message?: string }> {
|
||||
const url = `https://${this.apiDomain}/ca_apis/AgentManualDial`;
|
||||
|
||||
this.logger.log(`Kookoo outbound: dialing ${params.customerNumber}, conference room: ${roomId}`);
|
||||
this.logger.log(`Manual dial: agent=${params.agentId} campaign=${params.campaignName} number=${params.customerNumber}`);
|
||||
|
||||
try {
|
||||
// Call 1: Call the customer → put in conference room
|
||||
const customerParams = new URLSearchParams({
|
||||
phone_no: params.customerNumber,
|
||||
api_key: this.apiKey,
|
||||
outbound_version: '2',
|
||||
caller_id: callerId,
|
||||
callback_url: params.callbackUrl ?? `${callbackBase}/webhooks/kookoo/callback`,
|
||||
extra_data: `<response><playtext>Please wait while we connect you to an agent</playtext><conference>${roomId}</conference></response>`,
|
||||
const token = await this.getToken();
|
||||
const response = await axios.post(url, {
|
||||
userName: this.accountId,
|
||||
agentID: params.agentId,
|
||||
campaignName: params.campaignName,
|
||||
customerNumber: params.customerNumber,
|
||||
UCID: 'true',
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Call 2: Call the DID (which routes to agent via CloudAgent) → put in same conference room
|
||||
const agentParams = new URLSearchParams({
|
||||
phone_no: callerId,
|
||||
api_key: this.apiKey,
|
||||
outbound_version: '2',
|
||||
caller_id: params.customerNumber,
|
||||
extra_data: `<response><conference>${roomId}</conference></response>`,
|
||||
});
|
||||
this.logger.log(`Manual dial response: ${JSON.stringify(response.data)}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Manual dial failed: ${error.message} ${responseData}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire both calls
|
||||
const [customerRes, agentRes] = await Promise.all([
|
||||
axios.get(`https://in1-cpaas.ozonetel.com/outbound/outbound.php?${customerParams.toString()}`),
|
||||
axios.get(`https://in1-cpaas.ozonetel.com/outbound/outbound.php?${agentParams.toString()}`),
|
||||
]);
|
||||
async changeAgentState(params: {
|
||||
agentId: string;
|
||||
state: 'Ready' | 'Pause';
|
||||
pauseReason?: string;
|
||||
}): Promise<{ status: string; message: string }> {
|
||||
const url = `https://${this.apiDomain}/ca_apis/changeAgentState`;
|
||||
|
||||
const responseText = typeof customerRes.data === 'string' ? customerRes.data : String(customerRes.data);
|
||||
const agentResponseText = typeof agentRes.data === 'string' ? agentRes.data : String(agentRes.data);
|
||||
this.logger.log(`Kookoo customer dial: ${responseText}`);
|
||||
this.logger.log(`Kookoo agent dial: ${agentResponseText}`);
|
||||
this.logger.log(`Changing agent ${params.agentId} state to ${params.state}`);
|
||||
|
||||
// Parse XML response: <response><status>queued</status><message>SID</message></response>
|
||||
const statusMatch = responseText.match(/<status>(.*?)<\/status>/);
|
||||
const messageMatch = responseText.match(/<message>(.*?)<\/message>/);
|
||||
const status = statusMatch?.[1] ?? 'unknown';
|
||||
const message = messageMatch?.[1] ?? responseText;
|
||||
|
||||
if (status === 'error') {
|
||||
throw new Error(message);
|
||||
try {
|
||||
const body: Record<string, string> = {
|
||||
userName: this.accountId,
|
||||
agentId: params.agentId,
|
||||
state: params.state,
|
||||
};
|
||||
if (params.pauseReason) {
|
||||
body.pauseReason = params.pauseReason;
|
||||
}
|
||||
|
||||
return { status, message };
|
||||
const token = await this.getToken();
|
||||
const response = await axios.post(url, body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Change agent state response: ${JSON.stringify(response.data)}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Kookoo dial failed: ${error.message}`);
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Change agent state failed: ${error.message} ${responseData}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setDisposition(params: {
|
||||
agentId: string;
|
||||
ucid: string;
|
||||
disposition: string;
|
||||
}): Promise<{ status: string; message?: string; details?: string }> {
|
||||
const url = `https://${this.apiDomain}/ca_apis/DispositionAPIV2`;
|
||||
const did = process.env.OZONETEL_DID ?? '918041763265';
|
||||
|
||||
this.logger.log(`Set disposition: agent=${params.agentId} ucid=${params.ucid} disposition=${params.disposition}`);
|
||||
|
||||
try {
|
||||
const token = await this.getToken();
|
||||
const response = await axios.post(url, {
|
||||
userName: this.accountId,
|
||||
agentID: params.agentId,
|
||||
did,
|
||||
ucid: params.ucid,
|
||||
action: 'Set',
|
||||
disposition: params.disposition,
|
||||
autoRelease: 'true',
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Set disposition response: ${JSON.stringify(response.data)}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
const responseData = error?.response?.data ? JSON.stringify(error.response.data) : '';
|
||||
this.logger.error(`Set disposition failed: ${error.message} ${responseData}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user