mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
Endpoints: - GET /api/supervisor/barge/sip-credentials — fetch SIP number from pool - POST /api/supervisor/barge — initiate barge via Ozonetel apiId 63 - POST /api/supervisor/barge/mode — update mode (listen/whisper/barge) - POST /api/supervisor/barge/end — cleanup session + Redis SupervisorService extended with barge session tracking (in-memory Map). Mode changes emit SSE events to agent: supervisor-whisper, supervisor-barge, supervisor-left. Listen mode is silent (no event to agent). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
164 lines
6.4 KiB
TypeScript
164 lines
6.4 KiB
TypeScript
import { Controller, Post, Get, Body, HttpException, Logger } from '@nestjs/common';
|
|
import axios from 'axios';
|
|
import { OzonetelAdminAuthService } from '../ozonetel/ozonetel-admin-auth.service';
|
|
import { SupervisorService } from './supervisor.service';
|
|
import { TelephonyConfigService } from '../config/telephony-config.service';
|
|
|
|
// Supervisor barge/whisper/listen endpoints.
|
|
// Proxies requests to Ozonetel's dashboardApi using admin JWT auth.
|
|
//
|
|
// API reference (from CA-Admin source code):
|
|
// apiId 63 → CALL_BARGEIN (initiate barge)
|
|
// apiId 158 → Redis barge state (insert/delete)
|
|
// apiId 139 → SIP credential pool (sipSubscribe)
|
|
|
|
@Controller('api/supervisor/barge')
|
|
export class SupervisorBargeController {
|
|
private readonly logger = new Logger(SupervisorBargeController.name);
|
|
private readonly dashboardApiUrl = 'https://api.cloudagent.ozonetel.com/dashboardApi/monitor/api';
|
|
private readonly adminApiUrl = 'https://api.cloudagent.ozonetel.com/ca-admin-Api/CloudAgentAPI';
|
|
|
|
constructor(
|
|
private readonly adminAuth: OzonetelAdminAuthService,
|
|
private readonly supervisor: SupervisorService,
|
|
private readonly telephony: TelephonyConfigService,
|
|
) {}
|
|
|
|
@Get('sip-credentials')
|
|
async getSipCredentials() {
|
|
if (!this.adminAuth.isConfigured()) {
|
|
throw new HttpException('Ozonetel admin not configured — add credentials in Settings → Telephony', 503);
|
|
}
|
|
|
|
const config = this.telephony.getConfig();
|
|
const sipGateway = `${config.sip.domain}:${config.sip.wsPort}`;
|
|
const headers = await this.adminAuth.getAuthHeaders();
|
|
|
|
try {
|
|
const res = await axios.post(`${this.adminApiUrl}/endpoint/sipnumber/sipSubscribe`, {
|
|
apiId: 139,
|
|
sipURL: sipGateway,
|
|
}, { headers });
|
|
|
|
const data = res.data;
|
|
this.logger.log(`[BARGE] SIP credentials response: ${JSON.stringify(data)}`);
|
|
|
|
if (!data?.sip_number) {
|
|
throw new HttpException('No SIP numbers available in pool', 503);
|
|
}
|
|
|
|
return {
|
|
sipNumber: data.sip_number,
|
|
sipPassword: data.password,
|
|
sipDomain: data.pop_location ?? config.sip.domain,
|
|
sipPort: config.sip.wsPort,
|
|
};
|
|
} catch (err: any) {
|
|
this.logger.error(`[BARGE] SIP credentials failed: ${err.message}`);
|
|
if (err instanceof HttpException) throw err;
|
|
throw new HttpException('Failed to fetch SIP credentials', 502);
|
|
}
|
|
}
|
|
|
|
@Post()
|
|
async initiateBarge(@Body() body: { ucid: string; agentId: string; agentNumber: string; supervisorId?: string }) {
|
|
if (!body.ucid || !body.agentNumber) {
|
|
throw new HttpException('ucid and agentNumber required', 400);
|
|
}
|
|
if (!this.adminAuth.isConfigured()) {
|
|
throw new HttpException('Ozonetel admin not configured — add credentials in Settings → Telephony', 503);
|
|
}
|
|
|
|
// Prevent double-barge on same agent
|
|
const existing = this.supervisor.getBargeSession(body.agentId);
|
|
if (existing) {
|
|
throw new HttpException(`Agent ${body.agentId} is already being monitored`, 409);
|
|
}
|
|
|
|
// Get SIP credentials from Ozonetel pool
|
|
const sipCreds = await this.getSipCredentials();
|
|
const headers = await this.adminAuth.getAuthHeaders();
|
|
|
|
try {
|
|
const res = await axios.post(this.dashboardApiUrl, {
|
|
apiId: 63,
|
|
ucid: body.ucid,
|
|
action: 'CALL_BARGEIN',
|
|
isSip: true,
|
|
phoneno: sipCreds.sipNumber,
|
|
agentNumber: body.agentNumber,
|
|
cbURL: 'helix-engage',
|
|
}, { headers });
|
|
|
|
this.logger.log(`[BARGE] Initiated: ucid=${body.ucid} agent=${body.agentId} sip=${sipCreds.sipNumber} response=${JSON.stringify(res.data)}`);
|
|
|
|
// Track the session
|
|
this.supervisor.startBargeSession({
|
|
supervisorId: body.supervisorId ?? 'admin',
|
|
agentId: body.agentId,
|
|
sipNumber: sipCreds.sipNumber,
|
|
mode: 'listen',
|
|
startedAt: new Date().toISOString(),
|
|
});
|
|
|
|
return {
|
|
status: 'ok',
|
|
...sipCreds,
|
|
ozonetelResponse: res.data,
|
|
};
|
|
} catch (err: any) {
|
|
this.logger.error(`[BARGE] Initiation failed: ${err.message} ${err.response?.data ? JSON.stringify(err.response.data) : ''}`);
|
|
throw new HttpException(`Barge failed: ${err.response?.data?.Message ?? err.message}`, 502);
|
|
}
|
|
}
|
|
|
|
@Post('mode')
|
|
async updateMode(@Body() body: { agentId: string; mode: 'listen' | 'whisper' | 'barge' }) {
|
|
if (!body.agentId || !body.mode) {
|
|
throw new HttpException('agentId and mode required', 400);
|
|
}
|
|
if (!['listen', 'whisper', 'barge'].includes(body.mode)) {
|
|
throw new HttpException('mode must be listen, whisper, or barge', 400);
|
|
}
|
|
|
|
const session = this.supervisor.getBargeSession(body.agentId);
|
|
if (!session) {
|
|
throw new HttpException('No active barge session for this agent', 404);
|
|
}
|
|
|
|
this.supervisor.updateBargeMode(body.agentId, body.mode);
|
|
return { status: 'ok', mode: body.mode };
|
|
}
|
|
|
|
@Post('end')
|
|
async endBarge(@Body() body: { agentId: string }) {
|
|
if (!body.agentId) {
|
|
throw new HttpException('agentId required', 400);
|
|
}
|
|
|
|
const session = this.supervisor.getBargeSession(body.agentId);
|
|
if (!session) {
|
|
return { status: 'ok', message: 'No active session' };
|
|
}
|
|
|
|
// Clear Redis tracking on Ozonetel side (best-effort)
|
|
if (this.adminAuth.isConfigured()) {
|
|
try {
|
|
const headers = await this.adminAuth.getAuthHeaders();
|
|
await axios.post(this.dashboardApiUrl, {
|
|
apiId: 158,
|
|
Action: 'delete',
|
|
AgentId: body.agentId,
|
|
Sip: session.sipNumber,
|
|
}, { headers });
|
|
this.logger.log(`[BARGE] Redis cleanup: ${body.agentId}`);
|
|
} catch (err: any) {
|
|
this.logger.warn(`[BARGE] Redis cleanup failed (non-critical): ${err.message}`);
|
|
}
|
|
}
|
|
|
|
this.supervisor.endBargeSession(body.agentId);
|
|
return { status: 'ok' };
|
|
}
|
|
}
|