WIP - fix twilio functionality now that we use BFF

This commit is contained in:
Francisco Gaona
2026-02-05 02:41:54 +01:00
parent 49a571215d
commit 9226442525
6 changed files with 307 additions and 116 deletions

View File

@@ -31,46 +31,46 @@ export class VoiceService {
/**
* Get Twilio client for a tenant
*/
private async getTwilioClient(tenantIdOrDomain: string): Promise<{ client: Twilio.Twilio; config: TwilioConfig; tenantId: string }> {
private async getTwilioClient(tenantId: string): Promise<{ client: Twilio.Twilio; config: TwilioConfig; tenantId: string }> {
// Check cache first
if (this.twilioClients.has(tenantIdOrDomain)) {
if (this.twilioClients.has(tenantId)) {
const centralPrisma = getCentralPrisma();
// Look up tenant by domain
const domainRecord = await centralPrisma.domain.findUnique({
where: { domain: tenantIdOrDomain },
include: { tenant: { select: { id: true, integrationsConfig: true } } },
// Look up tenant by ID
const tenant = await centralPrisma.tenant.findUnique({
where: { id: tenantId },
select: { id: true, integrationsConfig: true },
});
const config = this.getIntegrationConfig(domainRecord?.tenant?.integrationsConfig as any);
const config = this.getIntegrationConfig(tenant?.integrationsConfig as any);
return {
client: this.twilioClients.get(tenantIdOrDomain),
client: this.twilioClients.get(tenantId),
config: config.twilio,
tenantId: domainRecord.tenant.id
tenantId: tenant.id
};
}
// Fetch tenant integrations config
const centralPrisma = getCentralPrisma();
this.logger.log(`Looking up domain: ${tenantIdOrDomain}`);
this.logger.log(`Looking up tenant: ${tenantId}`);
const domainRecord = await centralPrisma.domain.findUnique({
where: { domain: tenantIdOrDomain },
include: { tenant: { select: { id: true, integrationsConfig: true } } },
const tenant = await centralPrisma.tenant.findUnique({
where: { id: tenantId },
select: { id: true, integrationsConfig: true },
});
this.logger.log(`Domain record found: ${!!domainRecord}, Tenant: ${!!domainRecord?.tenant}, Config: ${!!domainRecord?.tenant?.integrationsConfig}`);
this.logger.log(`Tenant found: ${!!tenant}, Config: ${!!tenant?.integrationsConfig}`);
if (!domainRecord?.tenant) {
throw new Error(`Domain ${tenantIdOrDomain} not found`);
if (!tenant) {
throw new Error(`Tenant ${tenantId} not found`);
}
if (!domainRecord.tenant.integrationsConfig) {
if (!tenant.integrationsConfig) {
throw new Error('Tenant integrations config not found. Please configure Twilio credentials in Settings > Integrations');
}
const config = this.getIntegrationConfig(domainRecord.tenant.integrationsConfig as any);
const config = this.getIntegrationConfig(tenant.integrationsConfig as any);
this.logger.log(`Config decrypted: ${!!config.twilio}, AccountSid: ${config.twilio?.accountSid?.substring(0, 10)}..., AuthToken: ${config.twilio?.authToken?.substring(0, 10)}..., Phone: ${config.twilio?.phoneNumber}`);
@@ -79,9 +79,9 @@ export class VoiceService {
}
const client = Twilio.default(config.twilio.accountSid, config.twilio.authToken);
this.twilioClients.set(tenantIdOrDomain, client);
this.twilioClients.set(tenantId, client);
return { client, config: config.twilio, tenantId: domainRecord.tenant.id };
return { client, config: config.twilio, tenantId: tenant.id };
}
/**
@@ -105,22 +105,64 @@ export class VoiceService {
return {};
}
/**
* Find tenant by their configured Twilio phone number
* Used for inbound call routing
*/
async findTenantByPhoneNumber(phoneNumber: string): Promise<{ tenantId: string; config: TwilioConfig } | null> {
const centralPrisma = getCentralPrisma();
// Normalize phone number (remove spaces, ensure + prefix for comparison)
const normalizedPhone = phoneNumber.replace(/\s+/g, '').replace(/^(\d)/, '+$1');
this.logger.log(`Looking up tenant by phone number: ${normalizedPhone}`);
// Get all tenants with integrations config
const tenants = await centralPrisma.tenant.findMany({
where: {
integrationsConfig: { not: null },
},
select: { id: true, integrationsConfig: true },
});
for (const tenant of tenants) {
const config = this.getIntegrationConfig(tenant.integrationsConfig as any);
if (config.twilio?.phoneNumber) {
const tenantPhone = config.twilio.phoneNumber.replace(/\s+/g, '').replace(/^(\d)/, '+$1');
if (tenantPhone === normalizedPhone) {
this.logger.log(`Found tenant ${tenant.id} for phone number ${normalizedPhone}`);
return { tenantId: tenant.id, config: config.twilio };
}
}
}
this.logger.warn(`No tenant found for phone number: ${normalizedPhone}`);
return null;
}
/**
* Generate Twilio access token for browser Voice SDK
*/
async generateAccessToken(tenantDomain: string, userId: string): Promise<string> {
const { config, tenantId } = await this.getTwilioClient(tenantDomain);
async generateAccessToken(tenantId: string, userId: string): Promise<string> {
const { config, tenantId: resolvedTenantId } = await this.getTwilioClient(tenantId);
if (!config.accountSid || !config.apiKey || !config.apiSecret) {
throw new Error('Twilio API credentials not configured. Please add API Key and Secret in Settings > Integrations');
}
// Include tenantId in the identity so we can extract it in TwiML webhooks
// Format: tenantId:userId
const identity = `${resolvedTenantId}:${userId}`;
this.logger.log(`Generating access token with identity: ${identity}`);
this.logger.log(` Input tenantId: ${tenantId}, Resolved tenantId: ${resolvedTenantId}, userId: ${userId}`);
// Create an access token
const token = new AccessToken(
config.accountSid,
config.apiKey,
config.apiSecret,
{ identity: userId, ttl: 3600 } // 1 hour expiry
{ identity, ttl: 3600 } // 1 hour expiry
);
// Create a Voice grant