From 2666a10f48efebf655384a024b0209a57cc47057 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Thu, 16 Apr 2026 16:54:08 +0530 Subject: [PATCH] fix: await Ozonetel logout + per-agent sipPassword + campaign name on missed calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes: 1. Await Ozonetel logout in /auth/logout — prevents race condition when agent re-logs in quickly via "Remember me". The fire-and-forget logoutAgent() left a window where the next loginAgent() arrived while Ozonetel was still processing the previous logout, leaving the agent stuck in "Telephony Unavailable". (#559) 2. Use agentConfig.sipPassword (from Agent entity) instead of OZONETEL_AGENT_PASSWORD env var for login/logout/force-ready. The env var was a single shared credential that ignored per-agent passwords. Removed hardcoded "Test123$" fallback. Force-ready now looks up the Agent entity by ozonetelAgentId to get the correct sipPassword + sipExtension. 3. Missed-calls worklist query now fetches campaign { id campaignName } so the frontend Branch column can show the campaign name instead of the raw DID phone number. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/auth/auth.controller.ts | 12 ++++++++---- src/maint/maint.controller.ts | 25 +++++++++++++++++++------ src/worklist/worklist.service.ts | 1 + 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 11408b6..b33c316 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -138,10 +138,9 @@ export class AuthController { this.logger.warn(`Ozonetel token refresh on login failed: ${err.message}`); }); - const ozAgentPassword = this.telephony.getConfig().ozonetel.agentPassword || 'Test123$'; this.ozonetelAgent.loginAgent({ agentId: agentConfig.ozonetelAgentId, - password: ozAgentPassword, + password: agentConfig.sipPassword, phoneNumber: agentConfig.sipExtension, mode: 'blended', }).catch(err => { @@ -250,9 +249,14 @@ export class AuthController { await this.sessionService.unlockSession(agentConfig.ozonetelAgentId); this.logger.log(`Session unlocked for ${agentConfig.ozonetelAgentId}`); - this.ozonetelAgent.logoutAgent({ + // Await the Ozonetel logout so it completes before the + // HTTP response returns. Without this, a fast re-login + // (e.g. "remember me" auto-fill) races the logout and + // the agent lands in "Telephony Unavailable" because + // Ozonetel receives login while still processing logout. + await this.ozonetelAgent.logoutAgent({ agentId: agentConfig.ozonetelAgentId, - password: this.telephony.getConfig().ozonetel.agentPassword || 'Test123$', + password: agentConfig.sipPassword, }).catch(err => this.logger.warn(`Ozonetel logout failed: ${err.message}`)); this.agentConfigService.clearCache(memberId); diff --git a/src/maint/maint.controller.ts b/src/maint/maint.controller.ts index 4c9c90e..1553d45 100644 --- a/src/maint/maint.controller.ts +++ b/src/maint/maint.controller.ts @@ -31,13 +31,26 @@ export class MaintController { async forceReady(@Body() body: { agentId: string }) { if (!body?.agentId) throw new HttpException('agentId required', 400); const agentId = body.agentId; - const oz = this.telephony.getConfig().ozonetel; - const password = oz.agentPassword; - if (!password) throw new HttpException('agent password not configured', 400); - const sipId = oz.sipId; - if (!sipId) throw new HttpException('SIP ID not configured', 400); - this.logger.log(`[MAINT] Force ready: agent=${agentId}`); + // Look up the Agent entity to get sipPassword + sipExtension. + // Password comes from the Agent record, not an env var — each + // agent owns their own Ozonetel credential. + const agentData = await this.platform.query( + `{ agents(first: 1, filter: { ozonetelAgentId: { eq: "${agentId}" } }) { edges { node { + id sipExtension sipPassword + } } } }`, + ).catch(() => null); + + const agent = agentData?.agents?.edges?.[0]?.node; + if (!agent) throw new HttpException(`Agent ${agentId} not found in platform`, 404); + + const password = agent.sipPassword ?? agent.sipExtension; + if (!password) throw new HttpException(`Agent ${agentId} has no sipPassword configured`, 400); + + const sipId = agent.sipExtension; + if (!sipId) throw new HttpException(`Agent ${agentId} has no sipExtension configured`, 400); + + this.logger.log(`[MAINT] Force ready: agent=${agentId} ext=${sipId}`); try { await this.ozonetel.logoutAgent({ agentId, password }); diff --git a/src/worklist/worklist.service.ts b/src/worklist/worklist.service.ts index e5a1663..fc57c59 100644 --- a/src/worklist/worklist.service.ts +++ b/src/worklist/worklist.service.ts @@ -177,6 +177,7 @@ export class WorklistService { startedAt endedAt durationSec disposition leadId leadName callbackStatus callSourceNumber missedCallCount callbackAttemptedAt + campaign { id campaignName } } } pageInfo { hasNextPage endCursor } } }`, 'calls', authHeader,