diff --git a/apps/meteor/app/statistics/server/lib/getEEStatistics.ts b/apps/meteor/app/statistics/server/lib/getEEStatistics.ts index 9aaf20df0014c..1b8b825693aa7 100644 --- a/apps/meteor/app/statistics/server/lib/getEEStatistics.ts +++ b/apps/meteor/app/statistics/server/lib/getEEStatistics.ts @@ -5,8 +5,6 @@ import type { IStats } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { CannedResponse, OmnichannelServiceLevelAgreements, LivechatRooms, LivechatTag, LivechatUnit, Users } from '@rocket.chat/models'; -import { getVoIPStatistics } from './getVoIPStatistics'; - type ENTERPRISE_STATISTICS = IStats['enterprise']; type GenericStats = Pick; @@ -99,13 +97,6 @@ async function getEEStatistics(): Promise { }), ); - // TeamCollab VoIP data - statsPms.push( - getVoIPStatistics().then((voip) => { - statistics.voip = voip; - }), - ); - await Promise.all(statsPms).catch(log); return statistics as EEOnlyStats; diff --git a/apps/meteor/app/statistics/server/lib/getVoIPStatistics.ts b/apps/meteor/app/statistics/server/lib/getVoIPStatistics.ts deleted file mode 100644 index 99d574cdeed4c..0000000000000 --- a/apps/meteor/app/statistics/server/lib/getVoIPStatistics.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { log } from 'console'; - -import type { IStats, IVoIPPeriodStats } from '@rocket.chat/core-typings'; -import { FreeSwitchChannel } from '@rocket.chat/models'; -import { MongoInternals } from 'meteor/mongo'; - -import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; - -const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; - -const getMinDate = (days?: number): Date | undefined => { - if (!days) { - return; - } - - const date = new Date(); - date.setDate(date.getDate() - days); - - return date; -}; - -async function getVoIPStatisticsForPeriod(days?: number): Promise { - const promises: Array> = []; - const options = { - readPreference: readSecondaryPreferred(db), - }; - - const minDate = getMinDate(days); - - const statistics: IVoIPPeriodStats = {}; - - promises.push( - FreeSwitchChannel.countChannelsByKind('internal', minDate, options).then((count) => { - statistics.internalCalls = count; - }), - ); - - promises.push( - FreeSwitchChannel.countChannelsByKindAndDirection('external', 'inbound', minDate, options).then((count) => { - statistics.externalInboundCalls = count; - }), - ); - - promises.push( - FreeSwitchChannel.countChannelsByKindAndDirection('external', 'outbound', minDate, options).then((count) => { - statistics.externalOutboundCalls = count; - }), - ); - - promises.push( - FreeSwitchChannel.sumChannelsDurationByKind('internal', minDate, options).then((callsDuration) => { - statistics.callsDuration = callsDuration; - }), - ); - - promises.push( - FreeSwitchChannel.countChannelsByKindAndSuccessState('internal', true, minDate, options).then((count) => { - statistics.successfulCalls = count; - }), - ); - - promises.push( - FreeSwitchChannel.countChannelsByKindAndSuccessState('internal', false, minDate, options).then((count) => { - statistics.failedCalls = count; - }), - ); - - await Promise.allSettled(promises).catch(log); - - statistics.externalCalls = (statistics.externalInboundCalls || 0) + (statistics.externalOutboundCalls || 0); - statistics.calls = (statistics.successfulCalls || 0) + (statistics.failedCalls || 0); - - return statistics; -} - -export async function getVoIPStatistics(): Promise { - const statistics: IStats['enterprise']['voip'] = {}; - - const promises = [ - getVoIPStatisticsForPeriod().then((total) => { - statistics.total = total; - }), - getVoIPStatisticsForPeriod(30).then((month) => { - statistics.lastMonth = month; - }), - getVoIPStatisticsForPeriod(7).then((week) => { - statistics.lastWeek = week; - }), - getVoIPStatisticsForPeriod(1).then((day) => { - statistics.lastDay = day; - }), - ]; - - await Promise.allSettled(promises).catch(log); - - return statistics; -} diff --git a/apps/meteor/client/views/room/contextualBar/TeamsVoipConfigModal.tsx b/apps/meteor/client/views/room/contextualBar/TeamsVoipConfigModal.tsx index 201c4151abe55..0fe200a7aaff6 100644 --- a/apps/meteor/client/views/room/contextualBar/TeamsVoipConfigModal.tsx +++ b/apps/meteor/client/views/room/contextualBar/TeamsVoipConfigModal.tsx @@ -35,15 +35,11 @@ const TeamsVoipConfigModal = ({ onClose, onConfirm, isAdmin, hasModule }: TeamsV const teamsVoipConfigModalId = useId(); const getCalloutWarning = () => { - if (isAdmin && !hasModule) { + if (isAdmin) { return t('Contact_sales_start_using_VoIP'); } - if (!isAdmin && !hasModule) { - return t('Contact_your_workspace_admin_to_start_using_VoIP'); - } - - return t('VoIP_available_setup_freeswitch_server_details'); + return t('Contact_your_workspace_admin_to_start_using_VoIP'); }; return ( @@ -91,7 +87,7 @@ const TeamsVoipConfigModal = ({ onClose, onConfirm, isAdmin, hasModule }: TeamsV {t('Required_action')} - + {getCalloutWarning()} diff --git a/apps/meteor/ee/app/api-enterprise/server/index.ts b/apps/meteor/ee/app/api-enterprise/server/index.ts index 7a9a151aa1fc7..76c26e3c73974 100644 --- a/apps/meteor/ee/app/api-enterprise/server/index.ts +++ b/apps/meteor/ee/app/api-enterprise/server/index.ts @@ -1,2 +1 @@ import './canned-responses'; -import './voip-freeswitch'; diff --git a/apps/meteor/ee/app/api-enterprise/server/voip-freeswitch.ts b/apps/meteor/ee/app/api-enterprise/server/voip-freeswitch.ts deleted file mode 100644 index dd7999286de30..0000000000000 --- a/apps/meteor/ee/app/api-enterprise/server/voip-freeswitch.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { VoipFreeSwitch } from '@rocket.chat/core-services'; -import { Users } from '@rocket.chat/models'; -import { - isVoipFreeSwitchExtensionAssignProps, - isVoipFreeSwitchExtensionGetDetailsProps, - isVoipFreeSwitchExtensionGetInfoProps, - isVoipFreeSwitchExtensionListProps, -} from '@rocket.chat/rest-typings'; -import { wrapExceptions } from '@rocket.chat/tools'; - -import { API } from '../../../../app/api/server'; -import { settings } from '../../../../app/settings/server/cached'; - -API.v1.addRoute( - 'voip-freeswitch.extension.list', - { - authRequired: true, - permissionsRequired: ['manage-voip-extensions'], - validateParams: isVoipFreeSwitchExtensionListProps, - license: ['voip-enterprise'], - }, - { - async get() { - if (!settings.get('VoIP_TeamCollab_Enabled')) { - throw new Error('error-voip-disabled'); - } - - const { username, type = 'all' } = this.queryParams; - - const extensions = await wrapExceptions(() => VoipFreeSwitch.getExtensionList()).catch(() => { - throw new Error('error-loading-extension-list'); - }); - - if (type === 'all') { - return API.v1.success({ extensions }); - } - - const assignedExtensions = await Users.findAssignedFreeSwitchExtensions().toArray(); - - switch (type) { - case 'free': - const freeExtensions = extensions.filter(({ extension }) => !assignedExtensions.includes(extension)); - return API.v1.success({ extensions: freeExtensions }); - case 'allocated': - // Extensions that are already assigned to some user - const allocatedExtensions = extensions.filter(({ extension }) => assignedExtensions.includes(extension)); - return API.v1.success({ extensions: allocatedExtensions }); - case 'available': - // Extensions that are free or assigned to the specified user - const user = (username && (await Users.findOneByUsername(username, { projection: { freeSwitchExtension: 1 } }))) || undefined; - const currentExtension = user?.freeSwitchExtension; - - const availableExtensions = extensions.filter( - ({ extension }) => extension === currentExtension || !assignedExtensions.includes(extension), - ); - - return API.v1.success({ extensions: availableExtensions }); - } - - return API.v1.success({ extensions }); - }, - }, -); - -API.v1.addRoute( - 'voip-freeswitch.extension.assign', - { - authRequired: true, - permissionsRequired: ['manage-voip-extensions'], - validateParams: isVoipFreeSwitchExtensionAssignProps, - deprecation: { - version: '8.0.0', - alternatives: ['/v1/users.update'], - }, - license: ['voip-enterprise'], - }, - { - async post() { - if (!settings.get('VoIP_TeamCollab_Enabled')) { - throw new Error('error-voip-disabled'); - } - - const { extension, username } = this.bodyParams; - - if (!username) { - return API.v1.notFound(); - } - - const user = await Users.findOneByUsername(username, { projection: { freeSwitchExtension: 1 } }); - if (!user) { - return API.v1.notFound(); - } - - const existingUser = extension && (await Users.findOneByFreeSwitchExtension(extension, { projection: { _id: 1 } })); - if (existingUser && existingUser._id !== user._id) { - throw new Error('error-extension-not-available'); - } - - if (extension && user.freeSwitchExtension === extension) { - return API.v1.success(); - } - - await Users.setFreeSwitchExtension(user._id, extension); - return API.v1.success(); - }, - }, -); - -API.v1.addRoute( - 'voip-freeswitch.extension.getDetails', - { - authRequired: true, - permissionsRequired: ['view-voip-extension-details'], - validateParams: isVoipFreeSwitchExtensionGetDetailsProps, - license: ['voip-enterprise'], - }, - { - async get() { - if (!settings.get('VoIP_TeamCollab_Enabled')) { - throw new Error('error-voip-disabled'); - } - - const { extension, group } = this.queryParams; - - if (!extension) { - throw new Error('error-invalid-params'); - } - - const extensionData = await wrapExceptions(() => VoipFreeSwitch.getExtensionDetails({ extension, group })).suppress(() => undefined); - if (!extensionData) { - return API.v1.notFound(); - } - - const existingUser = await Users.findOneByFreeSwitchExtension(extensionData.extension, { projection: { username: 1, name: 1 } }); - - return API.v1.success({ - ...extensionData, - ...(existingUser && { userId: existingUser._id, name: existingUser.name, username: existingUser.username }), - }); - }, - }, -); - -API.v1.addRoute( - 'voip-freeswitch.extension.getRegistrationInfoByUserId', - { - authRequired: true, - permissionsRequired: ['view-user-voip-extension'], - validateParams: isVoipFreeSwitchExtensionGetInfoProps, - license: ['voip-enterprise'], - }, - { - async get() { - if (!settings.get('VoIP_TeamCollab_Enabled')) { - throw new Error('error-voip-disabled'); - } - - const { userId } = this.queryParams; - - if (!userId) { - throw new Error('error-invalid-params'); - } - - const user = await Users.findOneById(userId, { projection: { freeSwitchExtension: 1 } }); - if (!user) { - throw new Error('error-user-not-found'); - } - - const { freeSwitchExtension: extension } = user; - - if (!extension) { - throw new Error('error-extension-not-assigned'); - } - - const extensionData = await wrapExceptions(() => VoipFreeSwitch.getExtensionDetails({ extension })).suppress(() => undefined); - if (!extensionData) { - return API.v1.notFound('error-registration-not-found'); - } - const password = await wrapExceptions(() => VoipFreeSwitch.getUserPassword(extension)).suppress(() => undefined); - - return API.v1.success({ - extension: extensionData, - credentials: { - websocketPath: settings.get('VoIP_TeamCollab_FreeSwitch_WebSocket_Path'), - password, - }, - }); - }, - }, -); diff --git a/apps/meteor/ee/server/local-services/voip-freeswitch/service.ts b/apps/meteor/ee/server/local-services/voip-freeswitch/service.ts deleted file mode 100644 index de88d48495213..0000000000000 --- a/apps/meteor/ee/server/local-services/voip-freeswitch/service.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { type IVoipFreeSwitchService, ServiceClassInternal, ServiceStarter } from '@rocket.chat/core-services'; -import type { - FreeSwitchExtension, - IFreeSwitchChannelEvent, - IFreeSwitchChannel, - IFreeSwitchChannelEventDelta, -} from '@rocket.chat/core-typings'; -import { - getDomain, - getUserPassword, - getExtensionList, - getExtensionDetails, - parseEventData, - computeChannelFromEvents, - logger, - FreeSwitchEventClient, - type EventData, - type FreeSwitchOptions, -} from '@rocket.chat/freeswitch'; -import type { InsertionModel } from '@rocket.chat/model-typings'; -import { FreeSwitchChannel, FreeSwitchChannelEvent, FreeSwitchChannelEventDelta } from '@rocket.chat/models'; -import { wrapExceptions } from '@rocket.chat/tools'; -import type { InsertOneResult, WithoutId } from 'mongodb'; -import { MongoError } from 'mongodb'; - -import { settings } from '../../../../app/settings/server'; - -export class VoipFreeSwitchService extends ServiceClassInternal implements IVoipFreeSwitchService { - protected name = 'voip-freeswitch'; - - private serviceStarter: ServiceStarter; - - private eventClient: FreeSwitchEventClient | null = null; - - private wasEverConnected = false; - - constructor() { - super(); - - this.serviceStarter = new ServiceStarter( - async () => { - // Delay start to ensure setting values are up-to-date in the cache - setImmediate(() => this.startEvents()); - }, - async () => this.stopEvents(), - ); - this.onEvent('watch.settings', async ({ setting }): Promise => { - if (setting._id === 'VoIP_TeamCollab_Enabled') { - if (setting.value !== true) { - void this.serviceStarter.stop(); - return; - } - - if (setting.value === true) { - void this.serviceStarter.start(); - return; - } - } - - if (setting._id === 'VoIP_TeamCollab_FreeSwitch_Host') { - // Re-connect if the host changes - if (this.eventClient && this.eventClient.host !== setting.value) { - this.stopEvents(); - } - - if (setting.value) { - void this.serviceStarter.start(); - } - } - - // If any other freeswitch setting changes, only reconnect if it's not yet connected - if (setting._id.startsWith('VoIP_TeamCollab_FreeSwitch_')) { - if (!this.eventClient?.isReady()) { - this.stopEvents(); - void this.serviceStarter.start(); - } - } - }); - } - - public override async started(): Promise { - void this.serviceStarter.start(); - } - - private async startEvents(): Promise { - if (this.eventClient) { - if (!this.eventClient.isDone()) { - return; - } - - const client = this.eventClient; - this.eventClient = null; - client.endConnection(); - } - - const options = wrapExceptions(() => this.getConnectionSettings()).suppress(); - if (!options) { - this.wasEverConnected = false; - return; - } - - this.initializeEventClient(options); - } - - private retryEventsLater(): void { - // Try to re-establish connection after some time - setTimeout( - () => { - void this.startEvents(); - }, - this.wasEverConnected ? 3000 : 20_000, - ); - } - - private initializeEventClient(options: FreeSwitchOptions): void { - const client = FreeSwitchEventClient.listenToEvents(options); - this.eventClient = client; - - client.on('ready', () => { - if (this.eventClient !== client) { - return; - } - this.wasEverConnected = true; - }); - - client.on('end', () => { - if (this.eventClient && this.eventClient !== client) { - return; - } - - this.eventClient = null; - this.retryEventsLater(); - }); - - client.on('event', async ({ eventName, eventData }) => { - if (this.eventClient !== client) { - return; - } - - await wrapExceptions(() => - this.onFreeSwitchEvent(eventName as string, eventData as unknown as Record), - ).suppress(); - }); - } - - private stopEvents(): void { - if (!this.eventClient) { - return; - } - - this.eventClient.endConnection(); - this.wasEverConnected = false; - this.eventClient = null; - } - - private getConnectionSettings(): FreeSwitchOptions { - if (!settings.get('VoIP_TeamCollab_Enabled')) { - throw new Error('VoIP is disabled.'); - } - - const host = settings.get('VoIP_TeamCollab_FreeSwitch_Host'); - if (!host) { - throw new Error('VoIP is not properly configured.'); - } - - const port = settings.get('VoIP_TeamCollab_FreeSwitch_Port') || 8021; - const timeout = settings.get('VoIP_TeamCollab_FreeSwitch_Timeout') || 3000; - const password = settings.get('VoIP_TeamCollab_FreeSwitch_Password'); - - return { - socketOptions: { - host, - port, - }, - password, - timeout, - }; - } - - private async onFreeSwitchEvent(eventName: string, data: EventData): Promise { - const event = parseEventData(eventName, data); - if (!event) { - return; - } - - await this.registerEvent(event); - } - - private async registerRecord(registerFn: () => Promise): Promise { - try { - await registerFn(); - } catch (error) { - // avoid logging that an event was duplicated from mongo - if (error instanceof MongoError && error.code === 11000) { - return; - } - - logger.error(error); - throw error; - } - } - - private async registerEvent(event: InsertionModel>): Promise { - const { channelUniqueId, eventName } = event; - - if (eventName === 'CHANNEL_DESTROY' && channelUniqueId) { - // #TODO: Replace with a proper background process, also make it not rely on the CHANNEL_DESTROY event. - setTimeout(() => { - this.computeChannel(channelUniqueId).catch((reason) => { - logger.error({ msg: 'Failed to compute channel data ', reason, channelUniqueId }); - }); - }, 2000); - } - - return this.registerRecord(() => FreeSwitchChannelEvent.registerEvent(event)); - } - - private async registerChannel(channel: InsertionModel>): Promise { - return this.registerRecord(() => FreeSwitchChannel.registerChannel(channel)); - } - - private async registerChannelDelta(record: InsertionModel>): Promise { - return this.registerRecord(() => FreeSwitchChannelEventDelta.registerDelta(record)); - } - - private async computeChannel(channelUniqueId: string): Promise { - const allEvents = await FreeSwitchChannelEvent.findAllByChannelUniqueId(channelUniqueId).toArray(); - - const result = await computeChannelFromEvents(allEvents); - if (result?.channel) { - const { channel, deltas } = result; - - await this.registerChannel(channel); - - await Promise.allSettled(deltas.map(async (delta) => this.registerChannelDelta({ channelUniqueId: channel.uniqueId, ...delta }))); - } - } - - async getDomain(): Promise { - const options = this.getConnectionSettings(); - return getDomain(options); - } - - async getUserPassword(user: string): Promise { - const options = this.getConnectionSettings(); - return getUserPassword(options, user); - } - - async getExtensionList(): Promise { - const options = this.getConnectionSettings(); - return getExtensionList(options); - } - - async getExtensionDetails(requestParams: { extension: string; group?: string }): Promise { - const options = this.getConnectionSettings(); - return getExtensionDetails(options, requestParams); - } -} diff --git a/apps/meteor/ee/server/settings/voip.ts b/apps/meteor/ee/server/settings/voip.ts index f3d737b1e717b..17bfb149217cf 100644 --- a/apps/meteor/ee/server/settings/voip.ts +++ b/apps/meteor/ee/server/settings/voip.ts @@ -81,43 +81,6 @@ export function addSettings(): Promise { enableQuery, }); }); - - await this.section('VoIP_TeamCollab_FreeSwitch', async function () { - await this.add('VoIP_TeamCollab_FreeSwitch_Host', '', { - type: 'string', - public: false, - invalidValue: '', - enableQuery, - }); - - await this.add('VoIP_TeamCollab_FreeSwitch_Port', 8021, { - type: 'int', - public: false, - invalidValue: 8021, - enableQuery, - }); - - await this.add('VoIP_TeamCollab_FreeSwitch_Password', '', { - type: 'password', - secret: true, - invalidValue: '', - enableQuery, - }); - - await this.add('VoIP_TeamCollab_FreeSwitch_Timeout', 3000, { - type: 'int', - public: true, - invalidValue: 3000, - enableQuery, - }); - - await this.add('VoIP_TeamCollab_FreeSwitch_WebSocket_Path', '', { - type: 'string', - public: true, - invalidValue: '', - enableQuery, - }); - }); }, ); }); diff --git a/apps/meteor/ee/server/startup/services.ts b/apps/meteor/ee/server/startup/services.ts index 1d49d9ce20968..465bae22ef971 100644 --- a/apps/meteor/ee/server/startup/services.ts +++ b/apps/meteor/ee/server/startup/services.ts @@ -7,7 +7,6 @@ import { EnterpriseSettings } from '../../app/settings/server/settings.internalS import { InstanceService } from '../local-services/instance/service'; import { LDAPEEService } from '../local-services/ldap/service'; import { MessageReadsService } from '../local-services/message-reads/service'; -import { VoipFreeSwitchService } from '../local-services/voip-freeswitch/service'; // TODO consider registering these services only after a valid license is added api.registerService(new EnterpriseSettings()); @@ -15,7 +14,6 @@ api.registerService(new LDAPEEService()); api.registerService(new LicenseService()); api.registerService(new MessageReadsService()); api.registerService(new OmnichannelEE()); -api.registerService(new VoipFreeSwitchService()); // when not running micro services we want to start up the instance intercom if (!isRunningMs()) { diff --git a/apps/meteor/package.json b/apps/meteor/package.json index e3b68627ec2d4..ba6c8795e8f22 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -99,7 +99,6 @@ "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", "@rocket.chat/federation-sdk": "0.3.2", - "@rocket.chat/freeswitch": "workspace:^", "@rocket.chat/fuselage": "^0.69.0", "@rocket.chat/fuselage-forms": "~0.1.1", "@rocket.chat/fuselage-hooks": "~0.38.1", diff --git a/apps/meteor/server/models.ts b/apps/meteor/server/models.ts index cf6d328404835..3856396dc0d4b 100644 --- a/apps/meteor/server/models.ts +++ b/apps/meteor/server/models.ts @@ -21,9 +21,6 @@ import { FederationKeysRaw, FederationRoomEventsRaw, FederationServersRaw, - FreeSwitchChannelRaw, - FreeSwitchChannelEventRaw, - FreeSwitchChannelEventDeltaRaw, ImportDataRaw, ImportsModel, InstanceStatusRaw, @@ -106,9 +103,6 @@ registerModel('IExportOperationsModel', new ExportOperationsRaw(db)); registerModel('IFederationKeysModel', new FederationKeysRaw(db)); registerModel('IFederationRoomEventsModel', new FederationRoomEventsRaw(db)); registerModel('IFederationServersModel', new FederationServersRaw(db)); -registerModel('IFreeSwitchChannelModel', new FreeSwitchChannelRaw(db)); -registerModel('IFreeSwitchChannelEventModel', new FreeSwitchChannelEventRaw(db)); -registerModel('IFreeSwitchChannelEventDeltaModel', new FreeSwitchChannelEventDeltaRaw(db)); registerModel('IImportDataModel', new ImportDataRaw(db)); registerModel('IImportsModel', new ImportsModel(db)); registerModel('IInstanceStatusModel', new InstanceStatusRaw(db)); diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 2ee37894f8fdf..0792a9b5114ac 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -32,5 +32,6 @@ import './v323'; import './v324'; import './v325'; import './v326'; +import './v327'; export * from './xrun'; diff --git a/apps/meteor/server/startup/migrations/v327.ts b/apps/meteor/server/startup/migrations/v327.ts new file mode 100644 index 0000000000000..0de9f5883d3e1 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v327.ts @@ -0,0 +1,22 @@ +import { Settings } from '@rocket.chat/models'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 327, + name: 'Remove FreeSwitch Settings', + async up() { + await Settings.deleteMany({ + _id: { + $in: [ + 'VoIP_TeamCollab_FreeSwitch', + 'VoIP_TeamCollab_FreeSwitch_Host', + 'VoIP_TeamCollab_FreeSwitch_Port', + 'VoIP_TeamCollab_FreeSwitch_Password', + 'VoIP_TeamCollab_FreeSwitch_Timeout', + 'VoIP_TeamCollab_FreeSwitch_WebSocket_Path', + ], + }, + }); + }, +}); diff --git a/apps/meteor/tests/unit/server/lib/freeswitch.tests.ts b/apps/meteor/tests/unit/server/lib/freeswitch.tests.ts deleted file mode 100644 index c78568dcee7b1..0000000000000 --- a/apps/meteor/tests/unit/server/lib/freeswitch.tests.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import proxyquire from 'proxyquire'; -import sinon from 'sinon'; - -const { VoipFreeSwitchService } = proxyquire.noCallThru().load('../../../../ee/server/local-services/voip-freeswitch/service', { - '../../../../app/settings/server': { get: sinon.stub() }, -}); - -const VoipFreeSwitch = new VoipFreeSwitchService(); -// Those tests still need a proper freeswitch environment configured in order to run -// So for now they are being deliberately skipped on CI -describe.skip('VoIP', () => { - describe('FreeSwitch', () => { - it('should get a list of users from FreeSwitch', async () => { - const result = await VoipFreeSwitch.getExtensionList(); - - expect(result).to.be.an('array'); - expect(result[0]).to.be.an('object'); - expect(result[0].extension).to.be.a('string'); - }); - - it('should get a specific user from FreeSwitch', async () => { - const result = await VoipFreeSwitch.getExtensionDetails({ extension: '1001' }); - - expect(result).to.be.an('object'); - expect(result.extension).to.be.equal('1001'); - }); - - it('Should load user domain from FreeSwitch', async () => { - const result = await VoipFreeSwitch.getDomain(); - - expect(result).to.be.a('string').equal('rocket.chat'); - }); - - it('Should load user password from FreeSwitch', async () => { - const result = await VoipFreeSwitch.getUserPassword('1000'); - - expect(result).to.be.a('string'); - }); - }); -}); diff --git a/packages/core-services/src/index.ts b/packages/core-services/src/index.ts index dbaa89065b5ed..e3dff19790e55 100644 --- a/packages/core-services/src/index.ts +++ b/packages/core-services/src/index.ts @@ -46,7 +46,6 @@ import type { UiKitCoreAppPayload, IUiKitCoreApp, IUiKitCoreAppService } from '. import type { ISendFileLivechatMessageParams, ISendFileMessageParams, IUploadFileParams, IUploadService } from './types/IUploadService'; import type { IUserService } from './types/IUserService'; import type { IVideoConfService, VideoConferenceJoinOptions } from './types/IVideoConfService'; -import type { IVoipFreeSwitchService } from './types/IVoipFreeSwitchService'; export { AppStatusReport } from './types/IAppsEngineService'; export { asyncLocalStorage } from './lib/asyncLocalStorage'; @@ -121,7 +120,6 @@ export { IUiKitCoreApp, IUiKitCoreAppService, IVideoConfService, - IVoipFreeSwitchService, NPSCreatePayload, NPSVotePayload, proxify, @@ -162,7 +160,6 @@ export const MessageReads = proxify('message-reads'); export const Room = proxify('room'); export const Media = proxify('media'); export const MediaCall = proxify('media-call'); -export const VoipFreeSwitch = proxify('voip-freeswitch'); export const Analytics = proxify('analytics'); export const LDAP = proxify('ldap'); export const SAUMonitor = proxify('sau-monitor'); diff --git a/packages/core-services/src/types/IVoipFreeSwitchService.ts b/packages/core-services/src/types/IVoipFreeSwitchService.ts deleted file mode 100644 index 575cdb157969d..0000000000000 --- a/packages/core-services/src/types/IVoipFreeSwitchService.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; - -export interface IVoipFreeSwitchService { - getExtensionList(): Promise; - getExtensionDetails(requestParams: { extension: string; group?: string }): Promise; - getUserPassword(user: string): Promise; - getDomain(): Promise; -} diff --git a/packages/core-typings/src/index.ts b/packages/core-typings/src/index.ts index ddd09b3290467..5d2a97256fe22 100644 --- a/packages/core-typings/src/index.ts +++ b/packages/core-typings/src/index.ts @@ -109,7 +109,6 @@ export * from './ILivechatMonitorRecord'; export * from './ILivechatDepartment'; export * from './IOmnichannelAgent'; export * from './OmichannelRoutingConfig'; -export * from './voip'; export * from './IInquiry'; export * from './ILivechatPriority'; export * from './ILogs'; diff --git a/packages/core-typings/src/voip/FreeSwitchExtension.ts b/packages/core-typings/src/voip/FreeSwitchExtension.ts deleted file mode 100644 index 66e40f57fb666..0000000000000 --- a/packages/core-typings/src/voip/FreeSwitchExtension.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type FreeSwitchExtension = { - extension: string; - context?: string; - domain?: string; - groups: string[]; - status: 'UNKNOWN' | 'REGISTERED' | 'UNREGISTERED'; - contact?: string; - callGroup?: string; - callerName?: string; - callerNumber?: string; -}; diff --git a/packages/core-typings/src/voip/IFreeSwitchChannel.ts b/packages/core-typings/src/voip/IFreeSwitchChannel.ts deleted file mode 100644 index 88a165b8667a3..0000000000000 --- a/packages/core-typings/src/voip/IFreeSwitchChannel.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { IRocketChatRecord } from '../IRocketChatRecord'; -import type { - FreeSwitchChannelEventHeaderWithStates, - IFreeSwitchChannelEventLegProfile, - IFreeSwitchChannelEventMutable, -} from './IFreeSwitchChannelEvent'; - -export interface IFreeSwitchChannel extends IRocketChatRecord { - uniqueId: string; - name: string; - - freeSwitchUser?: string; - callers?: string[]; - callees?: string[]; - bridgedTo: string[]; - callDirection?: string; - - profiles: IFreeSwitchChannelProfile[]; - - startedAt: Date; - anyMedia: boolean; - anyAnswer: boolean; - anyBridge: boolean; - durationSum: number; - totalDuration: number; - - kind: 'internal' | 'external' | 'voicemail' | 'unknown'; - - finalState: IFreeSwitchChannelEventMutable; - events: FreeSwitchChannelEventHeaderWithStates[]; -} - -export interface IFreeSwitchChannelProfile extends IFreeSwitchChannelEventLegProfile { - // This value is pulled from the next profile - nextProfileCreatedTime?: Date; - - callDuration?: number; - answered?: boolean; - media?: boolean; - bridged?: boolean; -} diff --git a/packages/core-typings/src/voip/IFreeSwitchChannelEvent.ts b/packages/core-typings/src/voip/IFreeSwitchChannelEvent.ts deleted file mode 100644 index f894b4485d6ad..0000000000000 --- a/packages/core-typings/src/voip/IFreeSwitchChannelEvent.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { IRocketChatRecord } from '../IRocketChatRecord'; -import type { AtLeast } from '../utils'; - -export interface IFreeSwitchChannelEventHeader { - sequence: number; - eventName: string; - firedAt: Date; - receivedAt: Date; - - caller?: string; - callee?: string; -} - -export interface IFreeSwitchChannelEventStates { - channelState: string; - channelCallState: string; - originalChannelCallState?: string; - answerState?: string; -} - -export type FreeSwitchChannelEventHeaderWithStates = IFreeSwitchChannelEventHeader & IFreeSwitchChannelEventStates; - -export interface IFreeSwitchChannelEventMutable { - // uniqueId of the main channel in the active call, might not actually be unique between different calls. - // We overwrite this value in some events to ensure it always refer to the same uniqueId even while the call is not active. - callUniqueId: string; - - // the name of this channel on sofia, might reference the user or contact depending on which process created it - channelName: string; - - // For valid calls this should be parsed to a valid freeswitch username that is represented by this channel - // Parsing might fail for voicemail, spam bots and more advanced features we implement in the future. - channelUsername?: string; - - // Caller and Callee are calculated based on everything else, it's not any specific event attribute - caller?: string; - callee?: string; - - // Expected: CS_NEW, CS_INIT, CS_ROUTING, CS_EXECUTE, CS_EXCHANGE_MEDIA, CS_CONSUME_MEDIA, CS_HANGUP, CS_REPORTING, CS_DESTROY - // Not Expected: CS_SOFT_EXECUTE, CS_PARK, CS_HIBERNATE, CS_RESET, CS_NONE - channelState: string; - // Multiple state numbers may map to the same state name - channelStateNumber?: string; - // DOWN, DIALING, RINGING, EARLY, ACTIVE, HELD, RING_WAIT, HANGUP, UNHELD - channelCallState: string; - channelCallStateNumber?: string; - // The previous value of channelCallState - originalChannelCallState?: string; - // early, ringing, confirmed, answered, hangup, terminated - answerState?: string; - - // 'inbound' for the channel that initiated the call ("in" to freeswitch) - // 'outbound' for the channel(s) that are receiving the call ("out" from freeswitch) - callDirection?: string; - // will usually be true for the channel that initiated the call, if it is using the dialplan - channelHitDialplan?: string; - - hangupCause?: string; - bridgeUniqueIds?: string[]; - // Sent only by CALL_UPDATE events, when two channels are bridged together - bridgedTo?: string; - - legs: Record>; - - // variables should contain the same data you would get by running `uuid_dump` on fs_cli - variables?: Record; - - // raw will include fields we received from freeswitch but didn't read - raw: Record; - - codecs?: { - read?: { - name?: string; - rate?: string; - }; - write?: { - name?: string; - rate?: string; - }; - }; - - // Presence is something I'm trying to not depend on, but the info here could be useful for identifying users if there's no other reliable field. - channelPresenceId?: string; - presenceCallDirection?: string; -} - -export interface IFreeSwitchChannelEvent extends IRocketChatRecord, IFreeSwitchChannelEventHeader, IFreeSwitchChannelEventMutable { - channelUniqueId: string; - - metadata: Record; -} - -export interface IFreeSwitchChannelEventLegProfile { - // If profileIndex is a number higher than 1, then the channel is being reused for a second call - profileIndex?: string; - - profileCreatedTime?: Date; - channelCreatedTime?: Date; - channelAnsweredTime?: Date; - channelProgressTime?: Date; - channelBridgedTime?: Date; - channelProgressMediaTime?: Date; - channelHangupTime?: Date; - channelTransferTime?: Date; - channelRessurectTime?: Date; - channelLastHold?: Date; - - // Those are pulled from other places so that the profile can be mapped to specific calls - // They'll never be present on the raw events, only on the channel.events and channel.finalState.events - bridgedTo?: string; - caller?: string; - callee?: string; -} - -export interface IFreeSwitchChannelEventLeg { - // 'Caller' or 'Other-Leg'; Worthless information - legName: string; - // 'originator' or 'originatee', will be undefined if legName !== 'Other-Leg' - type?: string; - - // 'inbound' for the leg that initiated the call - // 'outbound' for the leg(s) that are receiving the call - direction: string; - // Logical direction is what the other leg would expect the direction of this leg to be - // If `direction` and `logicalDirection` are different, then the call was probably not started by either side (eg. server called both users) - logicalDirection: string; - - // Unreliable; Always the username of the user who initiated the first call in the chain, even if they are no longer involved. - username: string; - // Unreliable, same as username. - callerName: string; - // Unreliable, same as username. - callerNumber: string; - - // Unreliable, but not as much as username. - originalCallerName: string; - // Unreliable, but not as much as username. - originalCallerNumber: string; - - // Unreliable, same as username. - calleeName: string; - // Unreliable, same as username. - calleeNumber: string; - - networkAddress: string; - // Kinda reliable, but it can be so many different things depending on the event type, that it's not worth using - destinationNumber: string; - - // very reliable, always a channel's unique id - uniqueId: string; - // very reliable, always a valid channel name - channelName: string; - - // always 'mod_sofia' in our current use case - source: string; - // should match the context from the sip_profile ('default' for internal, 'public' for external), but I haven't tested it with external calls yet - context: string; - - transferSource?: string; - - profiles: Record; - - // always 'XML' in our current use case - dialplan?: string; - // Unreliable, same value as username; - ani?: string; - - // rdnis is present on transfered calls; Might be an username or a contact name. - rdnis?: string; - - // No use for those atm - screenBit?: string; - privacyHideName?: string; - privacyHideNumber?: string; - - raw?: Record; -} diff --git a/packages/core-typings/src/voip/IFreeSwitchChannelEventDelta.ts b/packages/core-typings/src/voip/IFreeSwitchChannelEventDelta.ts deleted file mode 100644 index 8ed95c34fb029..0000000000000 --- a/packages/core-typings/src/voip/IFreeSwitchChannelEventDelta.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { IRocketChatRecord } from '../IRocketChatRecord'; -import type { DeepPartial } from '../utils'; -import type { - IFreeSwitchChannelEventHeader, - IFreeSwitchChannelEventMutable, - IFreeSwitchChannelEventStates, -} from './IFreeSwitchChannelEvent'; - -type DeepModified = { - [P in keyof T]?: T[P] extends Date | undefined - ? { oldValue: T[P]; newValue: T[P]; delta: number } | undefined - : T[P] extends object | undefined - ? DeepModified - : { oldValue: T[P]; newValue: T[P] } | undefined; -}; - -export interface IFreeSwitchChannelEventDeltaData extends IFreeSwitchChannelEventHeader { - newValues?: DeepPartial; - modifiedValues?: DeepModified; -} - -export interface IFreeSwitchChannelEventDelta extends IRocketChatRecord, IFreeSwitchChannelEventDeltaData, IFreeSwitchChannelEventStates { - channelUniqueId: string; -} diff --git a/packages/core-typings/src/voip/index.ts b/packages/core-typings/src/voip/index.ts deleted file mode 100644 index e1f0e1dcd187c..0000000000000 --- a/packages/core-typings/src/voip/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './FreeSwitchExtension'; -export * from './IFreeSwitchChannel'; -export * from './IFreeSwitchChannelEvent'; -export * from './IFreeSwitchChannelEventDelta'; diff --git a/packages/freeswitch/.eslintrc.json b/packages/freeswitch/.eslintrc.json deleted file mode 100644 index 9ec331f09b5e3..0000000000000 --- a/packages/freeswitch/.eslintrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": ["@rocket.chat/eslint-config"], - "ignorePatterns": ["dist"] -} diff --git a/packages/freeswitch/CHANGELOG.md b/packages/freeswitch/CHANGELOG.md deleted file mode 100644 index c45bf23a74104..0000000000000 --- a/packages/freeswitch/CHANGELOG.md +++ /dev/null @@ -1,1123 +0,0 @@ -# @rocket.chat/freeswitch - -## 1.2.32 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.13.1 -
- -## 1.2.31 - -### Patch Changes - --
Updated dependencies [7f1b834a55b1240c226afde77713262da47f45dc, 5c7e8ec1de894e7b8eeb6e57b0c8a43bd22d2d46, 65fbcbed9f64004b953dd9d4182b3fccb8147339]: - - - @rocket.chat/core-typings@7.13.0 -
- -## 1.2.31-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.13.0-rc.2 -
- -> > > > > > > origin/master - -## 1.2.31-rc.1 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.13.0-rc.1 -
- -## 1.2.31-rc.0 - -### Patch Changes - --
Updated dependencies [7f1b834a55b1240c226afde77713262da47f45dc, 5c7e8ec1de894e7b8eeb6e57b0c8a43bd22d2d46, 65fbcbed9f64004b953dd9d4182b3fccb8147339]: - - - @rocket.chat/core-typings@7.13.0-rc.0 -
- -## 1.2.30 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.12.2 -
- -## 1.2.29 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.12.1 -
- -## 1.2.28 - -### Patch Changes - --
Updated dependencies [d166e2a1ffba4e59361d5f79e8c376fca5cbf12f]: - - - @rocket.chat/core-typings@7.12.0 -
- -## 1.2.28-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.12.0-rc.4 -
- -## 1.2.28-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.12.0-rc.3 -
- -## 1.2.28-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.12.0-rc.2 -
- -## 1.2.28-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.12.0-rc.1 -
- -## 1.2.28-rc.0 - -### Patch Changes - --
Updated dependencies [d166e2a1ffba4e59361d5f79e8c376fca5cbf12f]: - - - @rocket.chat/core-typings@7.12.0-rc.0 -
- -## 1.2.27 - -### Patch Changes - --
Updated dependencies [b0a4602a4461200b9872b2b073ec56fa55ecb466]: - - - @rocket.chat/core-typings@7.11.0 -
- -## 1.2.27-rc.7 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.7 -
- -## 1.2.27-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.6 -
- -## 1.2.26-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.5 -
- -## 1.2.26-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.4 -
- -## 1.2.26-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.3 -
- -## 1.2.26-rc.2 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.11.0-rc.2 -
- -## 1.2.26-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.11.0-rc.1 -
- -## 1.2.26-rc.0 - -### Patch Changes - --
Updated dependencies [b0a4602a4461200b9872b2b073ec56fa55ecb466]: - - - @rocket.chat/core-typings@7.11.0-rc.0 -
- -## 1.2.26 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.10.2 -
- -## 1.2.25 - -### Patch Changes - --
Updated dependencies []: -- @rocket.chat/core-typings@7.10.1 -
- -## 1.2.24 - -### Patch Changes - --
Updated dependencies [17bca96ecbf23ea807aba2e6e8abc95ebd66b0d0, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]: - - - @rocket.chat/core-typings@7.10.0 -
- -## 1.2.24-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.10.0-rc.6 -
- -## 1.2.24-rc.5 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.10.0-rc.5 -
- -## 1.2.21-rc.4 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.10.0-rc.4 -
- -## 1.2.21-rc.3 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.10.0-rc.3 -
- -## 1.2.21-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.10.0-rc.2 -
- -## 1.2.21-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.10.0-rc.1 -
- -## 1.2.21-rc.0 - -### Patch Changes - --
Updated dependencies [17bca96ecbf23ea807aba2e6e8abc95ebd66b0d0, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]: - - - @rocket.chat/core-typings@7.10.0-rc.0 -
- -## 1.2.23 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.9.3 -
- -## 1.2.22 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.9.2 -
- -## 1.2.21 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.9.1 -
- -## 1.2.20 - -### Patch Changes - -- ([#36230](https://github.com/RocketChat/Rocket.Chat/pull/36230)) Fixes FreeSwitch event parser to automatically reconnect when connection is lost - -- ([#35006](https://github.com/RocketChat/Rocket.Chat/pull/35006)) Fixes the parsing of FreeSwitch events to properly generate a history of calls on Rocket.Chat - --
Updated dependencies [2cec8acd5beddf5ad0c67c29fe632487cb82b026, fd478a7d45a4505ad53d2d7aec8b44e9bf8fa41a]: - - - @rocket.chat/core-typings@7.9.0 - - @rocket.chat/tools@0.2.3 -
- -## 1.2.20-rc.2 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.9.0-rc.2 -
- -## 1.2.20-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.9.0-rc.1 -
- -## 1.2.20-rc.0 - -### Patch Changes - -- ([#36230](https://github.com/RocketChat/Rocket.Chat/pull/36230)) Fixes FreeSwitch event parser to automatically reconnect when connection is lost - -- ([#35006](https://github.com/RocketChat/Rocket.Chat/pull/35006)) Fixes the parsing of FreeSwitch events to properly generate a history of calls on Rocket.Chat - --
Updated dependencies [2cec8acd5beddf5ad0c67c29fe632487cb82b026, fd478a7d45a4505ad53d2d7aec8b44e9bf8fa41a]: - - - @rocket.chat/core-typings@7.9.0-rc.0 - - @rocket.chat/tools@0.2.3-rc.0 -
- -## 1.2.19 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.3 -
- -## 1.2.18 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.2 -
- -## 1.2.17 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.1 -
- -## 1.2.16 - -### Patch Changes - --
Updated dependencies [3d024a900426c8bbf646e7ebedce0e17c9f7c140, 3779de0e8c5787f266bdeda5052b27c023c65f1c]: - - - @rocket.chat/core-typings@7.8.0 -
- -## 1.2.16-rc.9 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.9 -
- -## 1.2.13-rc.8 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.8 -
- -## 1.2.13-rc.7 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.7 -
- -## 1.2.13-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.6 -
- -## 1.2.13-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.5 -
- -## 1.2.13-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.4 -
- -## 1.2.13-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.3 -
- -## 1.2.13-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.8.0-rc.2 -
- -## 1.2.13-rc.1 - -## 1.2.16-rc.1 - -### Patch Changes - --
Updated dependencies []: -- @rocket.chat/core-typings@7.8.0-rc.1 -
- -## 1.2.13-rc.0 - -### Patch Changes - --
Updated dependencies [3d024a900426c8bbf646e7ebedce0e17c9f7c140, 3779de0e8c5787f266bdeda5052b27c023c65f1c]: - - - @rocket.chat/core-typings@7.8.0-rc.0 - -
- -## 1.2.15 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.7.4 -
- -## 1.2.14 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.3 -
- -## 1.2.13 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.2 - -
- -## 1.2.12 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.1 -
- -## 1.2.11 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0 -
- -## 1.2.11-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0-rc.6 -
- -## 1.2.11-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0-rc.5 -
- -## 1.2.11-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.1-rc.4 -
- -## 1.2.11-rc.3 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.7.0-rc.3 -
- -## 1.2.11-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0-rc.2 -
- -## 1.2.11-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0-rc.1 -
- -## 1.2.11-rc.0 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.7.0-rc.0 -
- -## 1.2.10 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.6.2 -
- -## 1.2.9 - -### Patch Changes - --
Updated dependencies []: -- @rocket.chat/core-typings@7.6.1 -
- -## 1.2.8 - -### Patch Changes - --
Updated dependencies [aec9eaa941fe9dad81f38d8d18d1b58edd700eb1, 2c190740d0ff166a4cefe8e833b0b2682a41fab1, d8eb824d242cbbeafb11b1c4a806860e4541ba79, bbd0b0d9ed181a156430e2a446d3b56092e3f645, 47ae69912cd90743e7bf836fdee4be481a01bbba, 4b28126ac94cf1d3312b30ad9863ca02673f49d4]: - - - @rocket.chat/core-typings@7.6.0 -
- -## 1.2.8-rc.8 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.8 -
- -## 1.2.8-rc.7 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.7 -
- -## 1.2.8-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.6 -
- -## 1.2.8-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.5 -
- -## 1.2.8-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.4 -
- -## 1.2.8-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.3 -
- -## 1.2.8-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.2 -
- -## 1.2.8-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.6.0-rc.1 -
- -## 1.2.8-rc.0 - -### Patch Changes - --
Updated dependencies [aec9eaa941fe9dad81f38d8d18d1b58edd700eb1, 2c190740d0ff166a4cefe8e833b0b2682a41fab1, d8eb824d242cbbeafb11b1c4a806860e4541ba79, bbd0b0d9ed181a156430e2a446d3b56092e3f645, 47ae69912cd90743e7bf836fdee4be481a01bbba, 4b28126ac94cf1d3312b30ad9863ca02673f49d4]: - - - @rocket.chat/core-typings@7.6.0-rc.0 -
- -## 1.2.7 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.1 -
- -## 1.2.6 - -### Patch Changes - --
Updated dependencies [25592391b04a5a9c5e4be57a3878bca7c7db66b2, c904862b1496cab943e97d28b36d3a24deac21c1]: - - - @rocket.chat/core-typings@7.5.0 -
- -## 1.2.6-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.0-rc.5 -
- -## 1.2.6-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.0-rc.4 -
- -## 1.2.6-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.0-rc.3 -
- -## 1.2.6-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.0-rc.2 -
- -## 1.2.6-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.5.0-rc.1 -
- -## 1.2.6-rc.0 - -### Patch Changes - --
Updated dependencies [25592391b04a5a9c5e4be57a3878bca7c7db66b2, c904862b1496cab943e97d28b36d3a24deac21c1]: - - - @rocket.chat/core-typings@7.5.0-rc.0 -
- -## 1.2.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.4.1 -
- -## 1.2.4 - -### Patch Changes - --
Updated dependencies [89964144e042c8d9282b51efd89e1e684077fdd7, f85da08765a9d3f8c5aabd9291fd08be6dfdeb85, be5031a21bdcda31270d53d319f7d183e77d84d7]: - - - @rocket.chat/core-typings@7.4.0 -
- -## 1.2.4-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.4.0-rc.5 -
- -## 1.2.4-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.4.0-rc.4 -
- -## 1.2.3-rc.3 - -### Patch Changes - --
Updated dependencies []: - - @rocket.chat/core-typings@7.4.0-rc.3 -
- -## 1.2.3-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.4.0-rc.2 -
- -## 1.2.3-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.4.0-rc.1 -
- -## 1.2.3-rc.0 - -### Patch Changes - --
Updated dependencies [89964144e042c8d9282b51efd89e1e684077fdd7, f85da08765a9d3f8c5aabd9291fd08be6dfdeb85, be5031a21bdcda31270d53d319f7d183e77d84d7]: - - - @rocket.chat/core-typings@7.4.0-rc.0 -
- -## 1.2.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.3 -
- -## 1.2.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.2 -
- -## 1.2.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.1 -
- -## 1.2.0 - -### Minor Changes - -- ([#34948](https://github.com/RocketChat/Rocket.Chat/pull/34948)) Adds voice calls data to statistics - -### Patch Changes - --
Updated dependencies [8942b0032af976738a7c602fa389803dda30c0dc, bfa92f4dba1a16973d7da5a9c0f5d0df998bf944]: - - - @rocket.chat/core-typings@7.3.0 -
- -## 1.2.0-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.0-rc.5 -
- -## 1.2.0-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.0-rc.4 -
- -## 1.2.0-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.0-rc.3 -
- -## 1.2.0-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.0-rc.2 -
- -## 1.2.0-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.3.0-rc.1 -
- -## 1.2.0-rc.0 - -### Minor Changes - -- ([#34948](https://github.com/RocketChat/Rocket.Chat/pull/34948)) Adds voice calls data to statistics - -### Patch Changes - --
Updated dependencies [8942b0032af976738a7c602fa389803dda30c0dc, bfa92f4dba1a16973d7da5a9c0f5d0df998bf944]: - - - @rocket.chat/core-typings@7.3.0-rc.0 -
- -## 1.1.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.2.1 -
- -## 1.1.0 - -### Minor Changes - -- ([#34004](https://github.com/RocketChat/Rocket.Chat/pull/34004)) Allows Rocket.Chat to store call events. - -### Patch Changes - --
Updated dependencies [76f6239ff1a9f34f163c03c140c4ceba62563b4e, 76f6239ff1a9f34f163c03c140c4ceba62563b4e, 475120dc19fb8cc400fd8af21559cd6f3cc17eb8, 2e4af86f6463166ba4d0b37b153b89ab246e112a, 76f6239ff1a9f34f163c03c140c4ceba62563b4e, 75a14b2e013aca7361cac56316f2b7e8c07d9dc8]: - - - @rocket.chat/core-typings@7.2.0 -
- -## 1.1.0-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.2.0-rc.3 -
- -## 1.1.0-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.2.0-rc.2 -
- -## 1.1.0-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.2.0-rc.1 -
- -## 1.1.0-rc.0 - -### Minor Changes - -- ([#34004](https://github.com/RocketChat/Rocket.Chat/pull/34004)) Allows Rocket.Chat to store call events. - -### Patch Changes - --
Updated dependencies [76f6239ff1a9f34f163c03c140c4ceba62563b4e, 76f6239ff1a9f34f163c03c140c4ceba62563b4e, 475120dc19fb8cc400fd8af21559cd6f3cc17eb8, 2e4af86f6463166ba4d0b37b153b89ab246e112a, 76f6239ff1a9f34f163c03c140c4ceba62563b4e, 75a14b2e013aca7361cac56316f2b7e8c07d9dc8]: - - - @rocket.chat/core-typings@7.2.0-rc.0 -
- -## 1.0.1 - -### Patch Changes - --
Updated dependencies [80e36bfc3938775eb26aa5576f1b9b98896e1cc4, 32d93a0666fa1cbe857d02889e93d9bbf45bd4f0]: - - - @rocket.chat/core-typings@7.1.0 -
- -## 1.0.1-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.1.0-rc.3 -
- -## 1.0.1-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.1.0-rc.2 -
- -## 1.0.1-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.1.0-rc.1 -
- -## 1.0.1-rc.0 - -### Patch Changes - --
Updated dependencies [80e36bfc3938775eb26aa5576f1b9b98896e1cc4, 32d93a0666fa1cbe857d02889e93d9bbf45bd4f0]: - - - @rocket.chat/core-typings@7.1.0-rc.0 -
- -## 1.0.0 - -### Major Changes - -- ([#33346](https://github.com/RocketChat/Rocket.Chat/pull/33346)) Implements integration with FreeSwitch to enable VoIP calls for team collaboration workspaces - -### Patch Changes - --
Updated dependencies [bcacbb1cee, b338807d76, 3ea02d3cc1, e3629e065b, 03d148524b, 81998f3450, 509143d6dd]: - - - @rocket.chat/core-typings@7.0.0 -
- -## 1.0.0-rc.6 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.6 -
- -## 1.0.0-rc.5 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.5 -
- -## 1.0.0-rc.4 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.4 -
- -## 1.0.0-rc.3 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.3 -
- -## 1.0.0-rc.2 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.2 -
- -## 1.0.0-rc.1 - -### Patch Changes - --
Updated dependencies []: - - - @rocket.chat/core-typings@7.0.0-rc.1 -
- -## 1.0.0-rc.0 - -### Major Changes - -- ([#33346](https://github.com/RocketChat/Rocket.Chat/pull/33346)) Implements integration with FreeSwitch to enable VoIP calls for team collaboration workspaces - -### Patch Changes - --
Updated dependencies [7726d68374, bcacbb1cee, b338807d76, 3ea02d3cc1, e3629e065b, 03d148524b, 81998f3450, 509143d6dd]: - - - @rocket.chat/core-typings@7.0.0-rc.0 -
diff --git a/packages/freeswitch/jest.config.ts b/packages/freeswitch/jest.config.ts deleted file mode 100644 index c18c8ae02465c..0000000000000 --- a/packages/freeswitch/jest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import server from '@rocket.chat/jest-presets/server'; -import type { Config } from 'jest'; - -export default { - preset: server.preset, -} satisfies Config; diff --git a/packages/freeswitch/package.json b/packages/freeswitch/package.json deleted file mode 100644 index ca879202a17bd..0000000000000 --- a/packages/freeswitch/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@rocket.chat/freeswitch", - "version": "1.2.32", - "private": true, - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "files": [ - "/dist" - ], - "scripts": { - "build": "rm -rf dist && tsc -p tsconfig.json", - "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput", - "lint": "eslint --ext .js,.jsx,.ts,.tsx .", - "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", - "test": "jest", - "testunit": "jest", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@rocket.chat/core-typings": "workspace:^", - "@rocket.chat/logger": "workspace:^", - "@rocket.chat/tools": "workspace:^", - "esl": "github:pierre-lehnen-rc/esl" - }, - "devDependencies": { - "@rocket.chat/jest-presets": "workspace:~", - "@rocket.chat/tsconfig": "workspace:*", - "@types/jest": "~30.0.0", - "eslint": "~8.45.0", - "jest": "~30.2.0", - "typescript": "~5.9.3" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/packages/freeswitch/src/FreeSwitchOptions.ts b/packages/freeswitch/src/FreeSwitchOptions.ts deleted file mode 100644 index 9a4a2d67cbdb5..0000000000000 --- a/packages/freeswitch/src/FreeSwitchOptions.ts +++ /dev/null @@ -1 +0,0 @@ -export type FreeSwitchOptions = { socketOptions: { host: string; port: number }; password: string; timeout?: number }; diff --git a/packages/freeswitch/src/commands/getDomain.ts b/packages/freeswitch/src/commands/getDomain.ts deleted file mode 100644 index b75aef37d7f18..0000000000000 --- a/packages/freeswitch/src/commands/getDomain.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { StringMap } from 'esl'; - -import type { FreeSwitchOptions } from '../FreeSwitchOptions'; -import { FreeSwitchApiClient } from '../esl'; -import { logger } from '../logger'; - -export function getCommandGetDomain(): string { - return 'eval ${domain}'; -} - -export function parseDomainResponse(response: StringMap): string { - const { _body: domain } = response; - - if (typeof domain !== 'string') { - logger.error({ msg: 'Failed to load user domain', response }); - throw new Error('Failed to load user domain from FreeSwitch.'); - } - - return domain; -} - -export async function getDomain(options: FreeSwitchOptions): Promise { - const response = await FreeSwitchApiClient.runSingleCommand(options, getCommandGetDomain()); - return parseDomainResponse(response); -} diff --git a/packages/freeswitch/src/commands/getExtensionDetails.ts b/packages/freeswitch/src/commands/getExtensionDetails.ts deleted file mode 100644 index ef85abdeed293..0000000000000 --- a/packages/freeswitch/src/commands/getExtensionDetails.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; - -import type { FreeSwitchOptions } from '../FreeSwitchOptions'; -import { FreeSwitchApiClient } from '../esl'; -import { mapUserData } from '../utils/mapUserData'; -import { parseUserList } from '../utils/parseUserList'; - -export function getCommandListFilteredUser(user: string, group = 'default'): string { - return `list_users group ${group} user ${user}`; -} - -export async function getExtensionDetails( - options: FreeSwitchOptions, - requestParams: { extension: string; group?: string }, -): Promise { - const { extension, group } = requestParams; - const response = await FreeSwitchApiClient.runSingleCommand(options, getCommandListFilteredUser(extension, group)); - - const users = parseUserList(response); - - if (!users.length) { - throw new Error('Extension not found.'); - } - - if (users.length >= 2) { - throw new Error('Multiple extensions were found.'); - } - - return mapUserData(users[0]); -} diff --git a/packages/freeswitch/src/commands/getExtensionList.ts b/packages/freeswitch/src/commands/getExtensionList.ts deleted file mode 100644 index 462ff68522888..0000000000000 --- a/packages/freeswitch/src/commands/getExtensionList.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; - -import type { FreeSwitchOptions } from '../FreeSwitchOptions'; -import { FreeSwitchApiClient } from '../esl'; -import { mapUserData } from '../utils/mapUserData'; -import { parseUserList } from '../utils/parseUserList'; - -export function getCommandListUsers(): string { - return 'list_users'; -} - -export async function getExtensionList(options: FreeSwitchOptions): Promise { - const response = await FreeSwitchApiClient.runSingleCommand(options, getCommandListUsers()); - const users = parseUserList(response); - - return users.map((item) => mapUserData(item)); -} diff --git a/packages/freeswitch/src/commands/getUserPassword.ts b/packages/freeswitch/src/commands/getUserPassword.ts deleted file mode 100644 index d8d08ba0ddac5..0000000000000 --- a/packages/freeswitch/src/commands/getUserPassword.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { StringMap } from 'esl'; - -import type { FreeSwitchOptions } from '../FreeSwitchOptions'; -import { logger } from '../logger'; -import { getCommandGetDomain, parseDomainResponse } from './getDomain'; -import { FreeSwitchApiClient } from '../esl'; - -export function getCommandGetUserPassword(user: string, domain = 'rocket.chat'): string { - return `user_data ${user}@${domain} param password`; -} - -export function parsePasswordResponse(response: StringMap): string { - const { _body: password } = response; - - if (password === undefined) { - logger.error({ msg: 'Failed to load user password', response }); - throw new Error('Failed to load user password from FreeSwitch.'); - } - - return password; -} - -export async function getUserPassword(options: FreeSwitchOptions, user: string): Promise { - return FreeSwitchApiClient.runCallback(options, async (runCommand) => { - const domainResponse = await runCommand(getCommandGetDomain()); - const domain = parseDomainResponse(domainResponse); - - const response = await runCommand(getCommandGetUserPassword(user, domain)); - return parsePasswordResponse(response); - }); -} diff --git a/packages/freeswitch/src/commands/index.ts b/packages/freeswitch/src/commands/index.ts deleted file mode 100644 index ac33235067db3..0000000000000 --- a/packages/freeswitch/src/commands/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './getDomain'; -export * from './getExtensionDetails'; -export * from './getExtensionList'; -export * from './getUserPassword'; diff --git a/packages/freeswitch/src/esl/apiClient.ts b/packages/freeswitch/src/esl/apiClient.ts deleted file mode 100644 index b4a25dbc08462..0000000000000 --- a/packages/freeswitch/src/esl/apiClient.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { FreeSwitchResponse, type FreeSwitchEventData, type StringMap } from 'esl'; - -import { logger } from '../logger'; -import { FreeSwitchESLClient, type FreeSwitchESLClientOptions } from './client'; - -export class FreeSwitchApiClient extends FreeSwitchESLClient { - private getCommandResponse(response: FreeSwitchEventData, command?: string): StringMap { - if (!response?.body) { - logger.error('No response from FreeSwitch server', command, response); - throw new Error('No response from FreeSwitch server.'); - } - - return response.body; - } - - protected override async transitionToReady(): Promise { - try { - this.response.event_json('BACKGROUND_JOB'); - } catch (error) { - logger.error({ msg: 'Failed to request api responses', error }); - throw new Error('failed-to-request-api-responses'); - } - - super.transitionToReady(); - } - - public async runCommand(command: string, timeout?: number): Promise { - await this.waitUntilUsable(); - - const result = await this.response.bgapi(command, timeout ?? FreeSwitchResponse.default_command_timeout); - return this.getCommandResponse(result, command); - } - - public static async runCallback( - options: FreeSwitchESLClientOptions, - cb: (runCommand: (command: string, timeout?: number) => Promise) => Promise, - ): Promise { - const client = new FreeSwitchApiClient(options); - try { - await client.waitUntilUsable(); - // Await result so it runs within the try..finally scope - const result = await cb(async (command: string, timeout?: number) => client.runCommand(command, timeout)); - - return result; - } finally { - client.endConnection(); - } - } - - public static async runSingleCommand(options: FreeSwitchESLClientOptions, command: string, timeout?: number): Promise { - return this.runCallback(options, async (runCommand) => runCommand(command, timeout)); - } -} diff --git a/packages/freeswitch/src/esl/client.ts b/packages/freeswitch/src/esl/client.ts deleted file mode 100644 index 1934ad28b3a4b..0000000000000 --- a/packages/freeswitch/src/esl/client.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Socket, type TcpSocketConnectOpts } from 'node:net'; - -import type { ValueOf } from '@rocket.chat/core-typings'; -import { Emitter } from '@rocket.chat/emitter'; -import { wrapExceptions } from '@rocket.chat/tools'; -import { FreeSwitchResponse, type StringMap } from 'esl'; - -import { logger } from '../logger'; - -export type EventNames = Parameters; - -export type FreeSwitchESLClientOptions = { - socketOptions: TcpSocketConnectOpts; - password: string; - timeout?: number; -}; - -export type FreeSwitchESLClientEvents = { - ready: void; - end: void; - event: { eventName: ValueOf; eventData: StringMap }; -}; - -export type FreeSwitchESLClientState = 'none' | 'connecting' | 'authenticating' | 'transitioning' | 'failed' | 'ready' | 'ended'; - -export class FreeSwitchESLClient extends Emitter { - private state: FreeSwitchESLClientState = 'none'; - - private socket: Socket; - - protected response: FreeSwitchResponse; - - private expectingEnd = false; - - public host: string | undefined; - - constructor(protected options: FreeSwitchESLClientOptions) { - super(); - this.host = this.options.socketOptions.host; - - logger.debug('Connecting new FreeSwitch socket'); - this.socket = new Socket(); - this.response = new FreeSwitchResponse(this.socket, logger); - - this.socket.once('connect', () => { - logger.debug('FreeSwitch socket connected.'); - this.authenticate(); - }); - - this.socket.once('error', (error) => { - logger.error({ msg: 'error on connection with freeswitch server', state: this.state, error }); - this.changeState('failed'); - }); - - this.socket.once('end', () => { - if (!this.expectingEnd) { - logger.debug('FreeSwitchESLClient received `end` event (remote end sent a FIN packet)'); - } - this.changeState('ended'); - }); - - this.socket.on('warning', (data) => { - logger.warn({ msg: 'FreeSwitchClient: warning', data }); - }); - - try { - this.socket.connect(this.options.socketOptions); - } catch (error) { - this.changeState('failed'); - logger.error({ msg: 'failed to connect to freeswitch server', error }); - } - } - - private async authenticate(): Promise { - logger.debug('FreeSwitch socket authenticating.'); - this.changeState('authenticating'); - - try { - // Wait for FreeSwitch to send us an authentication request - await this.response.onceAsync( - 'freeswitch_auth_request', - this.options.timeout ?? 20_000, - 'FreeSwitchClient expected authentication request', - ); - await this.response.auth(this.options.password); - - this.changeState('transitioning'); - - this.response.auto_cleanup(); - await this.transitionToReady(); - } catch (error) { - logger.error('FreeSwitchClient: initialization error', error); - this.changeState('failed'); - } - } - - protected async transitionToReady(): Promise { - this.changeState('ready'); - } - - protected changeState(newState: FreeSwitchESLClientState): void { - logger.debug({ msg: 'FreeSwitchESLClient changing state .', newState, state: this.state }); - if (this.isDone()) { - return; - } - - this.state = newState; - - if (this.isReady()) { - this.emit('ready'); - return; - } - - if (this.isDone()) { - this.emit('end'); - } - } - - public isReady(): boolean { - return this.state === 'ready'; - } - - public isDone(): boolean { - return ['failed', 'ended'].includes(this.state); - } - - public async waitUntilUsable(): Promise { - if (this.isReady()) { - return; - } - - if (this.isDone()) { - throw new Error('connection-ended'); - } - - return new Promise((resolve, reject) => { - let concluded = false; - this.once('ready', () => { - if (!concluded) { - concluded = true; - resolve(); - } - }); - - this.once('end', () => { - if (!concluded) { - concluded = true; - reject(new Error('connection-ended')); - } - }); - }); - } - - public endConnection(): void { - this.expectingEnd = true; - wrapExceptions(() => this.response.end()).suppress(); - } -} diff --git a/packages/freeswitch/src/esl/eventClient.ts b/packages/freeswitch/src/esl/eventClient.ts deleted file mode 100644 index b08f804c770a4..0000000000000 --- a/packages/freeswitch/src/esl/eventClient.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { logger } from '../logger'; -import { FreeSwitchESLClient, type EventNames, type FreeSwitchESLClientOptions } from './client'; - -const eventsToListen: EventNames = [ - 'CHANNEL_CALLSTATE', - 'CHANNEL_STATE', - 'CHANNEL_CREATE', - 'CHANNEL_DESTROY', - 'CHANNEL_ANSWER', - 'CHANNEL_HANGUP', - 'CHANNEL_HANGUP_COMPLETE', - 'CHANNEL_BRIDGE', - 'CHANNEL_UNBRIDGE', - 'CHANNEL_OUTGOING', - 'CHANNEL_PARK', - 'CHANNEL_UNPARK', - 'CHANNEL_HOLD', - 'CHANNEL_UNHOLD', - 'CHANNEL_ORIGINATE', - 'CHANNEL_UUID', - 'CHANNEL_APPLICATION', - 'CHANNEL_PROGRESS', - 'CHANNEL_PROGRESS_MEDIA', - 'CALL_UPDATE', -]; - -export class FreeSwitchEventClient extends FreeSwitchESLClient { - constructor( - protected override options: FreeSwitchESLClientOptions, - private eventsToListen: EventNames, - ) { - super(options); - - eventsToListen.forEach((eventName) => { - this.response.on(eventName, (eventData) => this.emit('event', { eventName, eventData: eventData.body })); - }); - } - - protected override async transitionToReady(): Promise { - try { - this.response.event_json(...this.eventsToListen); - } catch (error) { - logger.error({ msg: 'Failed to request events', error }); - throw new Error('failed-to-request-events'); - } - - super.transitionToReady(); - } - - public static listenToEvents(options: FreeSwitchESLClientOptions): FreeSwitchEventClient { - return new FreeSwitchEventClient(options, eventsToListen); - } -} diff --git a/packages/freeswitch/src/esl/index.ts b/packages/freeswitch/src/esl/index.ts deleted file mode 100644 index 7556203904066..0000000000000 --- a/packages/freeswitch/src/esl/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './apiClient'; -export * from './client'; -export * from './eventClient'; diff --git a/packages/freeswitch/src/eventParser/computeChannelFromEvents.ts b/packages/freeswitch/src/eventParser/computeChannelFromEvents.ts deleted file mode 100644 index 90b7470e748e7..0000000000000 --- a/packages/freeswitch/src/eventParser/computeChannelFromEvents.ts +++ /dev/null @@ -1,153 +0,0 @@ -import type { - IFreeSwitchChannel, - IFreeSwitchChannelEvent, - IFreeSwitchChannelEventDeltaData, - IFreeSwitchChannelEventHeader, - IFreeSwitchChannelEventMutable, - FreeSwitchChannelEventHeaderWithStates, - IFreeSwitchChannelEventStates, -} from '@rocket.chat/core-typings'; -import { convertPathsIntoSubObjects, convertSubObjectsIntoPaths } from '@rocket.chat/tools'; - -import { computeChannelProfiles } from './computeChannelProfiles'; -import { extractChannelChangesFromEvent } from './extractChannelChangesFromEvent'; -import { filterOutMissingData } from './filterOutMissingData'; -import { insertDataIntoEventProfile } from './insertDataIntoEventProfile'; -import { parseChannelKind } from './parseChannelKind'; - -function splitEventDataSections(event: IFreeSwitchChannelEvent): { - header: IFreeSwitchChannelEventHeader; - eventData: IFreeSwitchChannelEventMutable; - channelUniqueId: string; -} { - const { _id, channelUniqueId, _updatedAt, metadata, eventName, sequence, firedAt, receivedAt, callee, caller, ...eventData } = event; - - return { - channelUniqueId, - header: { - sequence, - eventName, - firedAt, - receivedAt, - callee, - caller, - }, - eventData, - }; -} - -export async function computeChannelFromEvents(allEvents: IFreeSwitchChannelEvent[]): Promise< - | { - channel: Omit; - deltas: (IFreeSwitchChannelEventDeltaData & IFreeSwitchChannelEventStates)[]; - } - | undefined -> { - if (!allEvents.length) { - return; - } - - const deltas: (IFreeSwitchChannelEventDeltaData & IFreeSwitchChannelEventStates)[] = []; - const { channelUniqueId: uniqueId, firedAt: firstEvent } = allEvents[0]; - const callDirections: string[] = []; - const callers: string[] = []; - const callees: string[] = []; - const bridgedTo: string[] = []; - const headers: FreeSwitchChannelEventHeaderWithStates[] = []; - - for (const event of allEvents) { - const { callee, caller, bridgeUniqueIds, bridgedTo: eventBridgedTo } = event; - - if (event.callDirection && !callDirections.includes(event.callDirection)) { - callDirections.push(event.callDirection); - } - if (callee && !callees.includes(callee)) { - callees.push(callee); - } - if (caller && !callers.includes(caller)) { - callers.push(caller); - } - - if (bridgeUniqueIds) { - for (const bridgeUniqueId of bridgeUniqueIds) { - if (bridgeUniqueId && !bridgedTo.includes(bridgeUniqueId) && bridgeUniqueId !== uniqueId) { - bridgedTo.push(bridgeUniqueId); - } - } - } - if (eventBridgedTo && !bridgedTo.includes(eventBridgedTo)) { - bridgedTo.push(eventBridgedTo); - } - } - - const flattened = allEvents.reduce( - (state, nextEvent: IFreeSwitchChannelEvent) => { - const { header, eventData, channelUniqueId } = splitEventDataSections(nextEvent); - const { caller, callee, eventName, sequence } = header; - - // Inserts the callee and bridgedTo attributes into the profile of this event - const eventDataEx = insertDataIntoEventProfile(eventData, { caller, callee, bridgedTo: eventData.bridgedTo }); - - // Make a list with every value from the event, except for the headers; - const eventValues = convertSubObjectsIntoPaths(eventDataEx); - - // Compare the event's list of values with the full list from all past events - const { changedValues, newValues, changedExistingValues } = extractChannelChangesFromEvent(state, eventName, eventValues); - - const { channelState, channelCallState, originalChannelCallState, answerState } = eventData; - const headerWithStates: FreeSwitchChannelEventHeaderWithStates = { - ...header, - channelState, - channelCallState, - originalChannelCallState, - answerState, - }; - - // Generate a "delta" entry with the data that has changed in this event - const delta: IFreeSwitchChannelEventDeltaData & IFreeSwitchChannelEventStates = { - ...headerWithStates, - - newValues: convertPathsIntoSubObjects(newValues), - modifiedValues: convertPathsIntoSubObjects(changedExistingValues), - }; - - // Store this delta in a list - deltas.push(filterOutMissingData(delta)); - headers.push(headerWithStates); - - return { - channelUniqueId, - ...state, - eventName, - sequence, - ...changedValues, - }; - }, - {} as Record, - ); - - const finalState = convertPathsIntoSubObjects(flattened) as IFreeSwitchChannelEventMutable; - - const computedProfiles = computeChannelProfiles(finalState?.legs?.[uniqueId]?.profiles || {}); - - return { - channel: { - uniqueId, - name: finalState.channelName, - callDirection: callDirections.join('||'), - freeSwitchUser: finalState.channelUsername, - callers, - callees, - bridgedTo, - ...{ - ...computedProfiles, - // If we couldn't parse a startedAt, use the time of the first event - startedAt: computedProfiles.startedAt || firstEvent, - }, - kind: parseChannelKind(finalState.channelName), - finalState, - events: headers, - }, - deltas, - }; -} diff --git a/packages/freeswitch/src/eventParser/computeChannelProfiles.ts b/packages/freeswitch/src/eventParser/computeChannelProfiles.ts deleted file mode 100644 index 3dfe92e68ac74..0000000000000 --- a/packages/freeswitch/src/eventParser/computeChannelProfiles.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* eslint-disable complexity */ -import type { IFreeSwitchChannelEventLegProfile, IFreeSwitchChannelProfile } from '@rocket.chat/core-typings'; - -function adjustProfileTimestamps(profile: IFreeSwitchChannelEventLegProfile): IFreeSwitchChannelEventLegProfile { - const { profileIndex, profileCreatedTime, channelCreatedTime, bridgedTo, caller, callee, ...timestamps } = profile; - - // Don't mutate anything if it's the first profile - if (!profileIndex || profileIndex === '1') { - return { ...profile }; - } - - const newProfile: IFreeSwitchChannelEventLegProfile = { - channelCreatedTime, - profileIndex, - bridgedTo, - callee, - caller, - }; - - // If we don't know when the profile was created, drop every other timestamp - if (!profileCreatedTime) { - return newProfile; - } - - newProfile.profileCreatedTime = profileCreatedTime; - - for (const key of Object.keys(timestamps)) { - const value = timestamps[key as keyof typeof timestamps]; - if (!value || typeof value === 'string') { - continue; - } - - if (value < profileCreatedTime) { - continue; - } - - newProfile[key as keyof typeof timestamps] = value; - } - - return newProfile; -} - -type ProfileListAndSummary = { - profiles: IFreeSwitchChannelProfile[]; - anyMedia: boolean; - anyAnswer: boolean; - anyBridge: boolean; - durationSum: number; - totalDuration: number; - - startedAt?: Date; -}; - -export function computeChannelProfiles(legProfiles: Record): ProfileListAndSummary { - const profiles: IFreeSwitchChannelProfile[] = Object.values(legProfiles).map((profile) => adjustProfileTimestamps(profile)); - - // Sort profiles by createdTime, temporarily filter out the ones that do not have one: - const sortedProfiles = profiles - .filter( - ({ profileCreatedTime, channelCreatedTime, profileIndex }) => profileCreatedTime || (profileIndex === '1' && channelCreatedTime), - ) - .sort( - ({ profileCreatedTime: profile1, channelCreatedTime: channel1 }, { profileCreatedTime: profile2, channelCreatedTime: channel2 }) => - (profile1?.valueOf() || (channel1 as Date).valueOf()) - (profile2?.valueOf() || (channel2 as Date).valueOf()), - ); - - const adjustedProfiles: IFreeSwitchChannelProfile[] = []; - let anyAnswer = false; - let anyMedia = false; - let anyBridge = false; - let durationSum = 0; - let firstProfileCreate: Date | undefined; - // "first" because it's an array, but it's the same channel for all so there should only be one value - let firstChannelCreate: Date | undefined; - - for (let i = 0; i < sortedProfiles.length; i++) { - const nextProfileCreatedTime = sortedProfiles[i + 1]?.profileCreatedTime || undefined; - - const profile = sortedProfiles[i]; - - const { - channelBridgedTime, - channelAnsweredTime, - channelProgressMediaTime, - channelHangupTime, - bridgedTo, - profileCreatedTime, - channelCreatedTime, - } = profile; - - const callEnd = channelHangupTime || nextProfileCreatedTime; - - if (channelCreatedTime && (!firstChannelCreate || firstChannelCreate > channelCreatedTime)) { - firstChannelCreate = channelCreatedTime; - } - - if (profileCreatedTime && (!firstProfileCreate || firstProfileCreate > profileCreatedTime)) { - firstProfileCreate = profileCreatedTime; - } - - const callDuration = callEnd && channelBridgedTime ? callEnd.valueOf() - channelBridgedTime.valueOf() : 0; - const media = Boolean(channelProgressMediaTime) || sortedProfiles.length > 1; - const answered = Boolean(channelAnsweredTime) || media; - const bridged = Boolean(channelBridgedTime) || Boolean(bridgedTo); - - anyMedia ||= media; - anyAnswer ||= answered; - anyBridge ||= bridged; - durationSum += callDuration; - - adjustedProfiles.push({ - ...profile, - ...{ - nextProfileCreatedTime, - callDuration, - answered, - media, - bridged, - }, - }); - } - - // Look for bridge and hangup on every channel, even if they didn't have a profile timestamp (in theory every profile will always have a created timestamp) - let firstBridge: Date | undefined; - let lastCallEnd: Date | undefined; - for (const profile of profiles) { - const { channelBridgedTime, channelHangupTime, nextProfileCreatedTime, bridgedTo } = profile; - const callEnd = channelHangupTime || nextProfileCreatedTime; - - if (channelBridgedTime && (!firstBridge || firstBridge > channelBridgedTime)) { - firstBridge = channelBridgedTime; - } - - if ((callEnd || 0) > (lastCallEnd || 0)) { - lastCallEnd = callEnd; - } - - // If this profile was filtered out from the list used by the first process, add it back to the final list here - if (!sortedProfiles.includes(profile)) { - const bridged = Boolean(channelBridgedTime) || Boolean(bridgedTo); - anyBridge ||= bridged; - - adjustedProfiles.push({ ...profile, ...{ bridged } }); - } - } - - const firstCallStart = firstBridge || firstProfileCreate || firstChannelCreate; - const totalDuration = lastCallEnd && firstCallStart ? lastCallEnd.valueOf() - firstCallStart.valueOf() : 0; - - return { - profiles: adjustedProfiles, - anyMedia, - anyAnswer, - anyBridge, - durationSum, - totalDuration, - startedAt: firstCallStart, - }; -} diff --git a/packages/freeswitch/src/eventParser/extractChannelChangesFromEvent.ts b/packages/freeswitch/src/eventParser/extractChannelChangesFromEvent.ts deleted file mode 100644 index 432660d98b203..0000000000000 --- a/packages/freeswitch/src/eventParser/extractChannelChangesFromEvent.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Make an object with a list of changes from the previous accumulated channel state and the state of the next event -export function extractChannelChangesFromEvent( - channelState: Record, - _eventName: string, - eventValues: Record, -): { - changedValues: Record; - newValues: Record; - changedExistingValues: Record; -} { - const changedValues: Record = {}; - const newValues: Record = {}; - const changedExistingValues: Record = {}; - - const getValues = (key: string): { oldValue: any; newValue: any } => { - const oldValue = channelState[key]; - const newValue = eventValues[key]; - - if (key !== 'bridgeUniqueIds') { - return { oldValue, newValue }; - } - - // For the bridgeUniqueIds field specifically, only add new values, never remove - const oldList = Array.isArray(oldValue) ? oldValue : [oldValue]; - const newList = Array.isArray(newValue) ? newValue : [newValue]; - const fullList = [...new Set([...oldList, ...newList])].filter((id) => id); - - return { oldValue, newValue: fullList }; - }; - - for (const key of Object.keys(eventValues)) { - const { oldValue, newValue } = getValues(key); - - if (newValue === undefined || oldValue === newValue) { - continue; - } - - const isDate = typeof newValue === 'object' && newValue instanceof Date && typeof oldValue === 'object' && oldValue instanceof Date; - - if (isDate && newValue.toISOString() === oldValue.toISOString()) { - continue; - } - - if (Array.isArray(oldValue) || Array.isArray(newValue)) { - const oldList = Array.isArray(oldValue) ? oldValue : [oldValue]; - const newList = Array.isArray(newValue) ? newValue : [newValue]; - - const isEqual = !oldList.some((item) => !newList.includes(item)) && !newList.some((item) => !oldList.includes(item)); - - if (key.startsWith('variables.') && isEqual) { - continue; - } - if (key === 'bridgeUniqueIds' && newList.length <= oldList.length) { - continue; - } - } - - if (oldValue === undefined) { - newValues[key] = newValue; - } else { - changedExistingValues[key] = { - oldValue, - newValue, - ...(isDate && { delta: newValue.valueOf() - oldValue.valueOf() }), - }; - } - - changedValues[key] = newValue; - } - - return { changedValues, newValues, changedExistingValues }; -} diff --git a/packages/freeswitch/src/eventParser/filterOutMissingData.ts b/packages/freeswitch/src/eventParser/filterOutMissingData.ts deleted file mode 100644 index ab81a6a61cdbb..0000000000000 --- a/packages/freeswitch/src/eventParser/filterOutMissingData.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { objectMap } from '@rocket.chat/tools'; - -export function filterOutMissingData>(data: T): T { - return objectMap( - data, - ({ key, value }) => { - if (typeof value !== 'boolean') { - if (!value || value === '0') { - return; - } - } - - if (typeof value === 'object' && !(value instanceof Date) && !Array.isArray(value) && !Object.keys(value).length) { - return; - } - - return { key, value }; - }, - true, - ) as T; -} diff --git a/packages/freeswitch/src/eventParser/filterStringList.ts b/packages/freeswitch/src/eventParser/filterStringList.ts deleted file mode 100644 index 8c8ac39770596..0000000000000 --- a/packages/freeswitch/src/eventParser/filterStringList.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { EventData } from './parseEventData'; - -export function filterStringList( - object: EventData, - filterFn: (key: string) => boolean, - mapFn?: (data: [string, string | string[] | undefined]) => [string, string | string[] | undefined] | undefined, -): EventData { - const filteredEntries = Object.entries(object).filter(([key]) => filterFn(key)); - - if (!mapFn) { - return Object.fromEntries(filteredEntries) as EventData; - } - - const mappedEntries = filteredEntries.map(mapFn).filter((entry) => entry) as [string, string][]; - return Object.fromEntries(mappedEntries); -} diff --git a/packages/freeswitch/src/eventParser/insertDataIntoEventProfile.ts b/packages/freeswitch/src/eventParser/insertDataIntoEventProfile.ts deleted file mode 100644 index 5cbd954ea1ae6..0000000000000 --- a/packages/freeswitch/src/eventParser/insertDataIntoEventProfile.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { IFreeSwitchChannelEventMutable, IFreeSwitchChannelEventLegProfile } from '@rocket.chat/core-typings'; -import { isRecord } from '@rocket.chat/tools'; - -/** - * Returns a soft-copy of the eventData, with the specified data inserted into the profile of the channel's main leg, if it exists. - * While this returns a new object and the original is not mutated, the result is not a complete hard copy and may still include references to the original - */ -export function insertDataIntoEventProfile( - eventData: IFreeSwitchChannelEventMutable, - dataToInsertIntoProfile: Partial>, -): IFreeSwitchChannelEventMutable { - if (!isRecord(eventData.legs)) { - return eventData; - } - - const clonedData = { - ...eventData, - // Clone each leg individually, as we might mutate it - legs: Object.fromEntries( - Object.entries(eventData.legs).map(([key, leg]) => [ - key, - { ...leg, ...(isRecord(leg.profiles) && { profiles: { ...leg.profiles } }) }, - ]), - ), - }; - - for (const leg of Object.values(clonedData.legs)) { - if (!isRecord(leg?.profiles)) { - continue; - } - - // The raw event can never have more than one profile at the same time, it's only a record because the key for the profile can change between events - const legProfileKey = Object.keys(leg.profiles).pop(); - - if (legProfileKey && isRecord(leg.profiles[legProfileKey])) { - leg.profiles[legProfileKey] = { - ...leg.profiles[legProfileKey], - ...dataToInsertIntoProfile, - }; - } - } - - return clonedData; -} diff --git a/packages/freeswitch/src/eventParser/parseChannelKind.ts b/packages/freeswitch/src/eventParser/parseChannelKind.ts deleted file mode 100644 index 3d0018c826999..0000000000000 --- a/packages/freeswitch/src/eventParser/parseChannelKind.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { IFreeSwitchChannel } from '@rocket.chat/core-typings'; - -export function parseChannelKind(channelName?: string): IFreeSwitchChannel['kind'] { - if (!channelName) { - return 'unknown'; - } - - if (channelName.startsWith('sofia/internal/')) { - return 'internal'; - } - - if (channelName.startsWith('sofia/external/')) { - return 'external'; - } - - if (channelName.startsWith('loopback/voicemail')) { - return 'voicemail'; - } - - return 'unknown'; -} diff --git a/packages/freeswitch/src/eventParser/parseChannelUsername.ts b/packages/freeswitch/src/eventParser/parseChannelUsername.ts deleted file mode 100644 index feb334cebb9ef..0000000000000 --- a/packages/freeswitch/src/eventParser/parseChannelUsername.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Gets the FreeSwitch username associated with the channel that an event was triggered for - * - * By design of our integration, FreeSwitch usernames are always equal to the User's Extension Number. - * So effectively this returns an extension number. - */ - -import { logger } from '../logger'; -import type { EventData } from './parseEventData'; - -export function parseChannelUsername(channelName?: string): string | undefined { - if (!channelName || channelName.startsWith('loopback/')) { - return; - } - - // If it's not a sofia channel, don't even try to parse it - // It's most likely a voicemail or maybe some spam bots trying different stuff - // If we implement other kinds of channels in the future we should look into how their names are generated so that we may parse them here too. - // The format for external channels may depend on what the external service is, but extension@host should be quite standard - if (!channelName.startsWith('sofia/') || !channelName.includes('@')) { - logger.debug({ msg: 'FreeSwitch event triggered with something other than a sofia or loopback channel.', channelName }); - return; - } - - // Originator channels will have the format 'sofia/internal/username@freeswitch_host', assigned by freeswitch itself - // Example: sofia/internal/1001@voip.open.rocket.chat:9999 - - // Originatee channels will have the format 'sofia/internal/contact_uri', assigned by freeswitch itself - // Example: sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-spo254ol@open.rocket.chat - - return channelName.match(/sofia\/\S+\/(\d+)[\@\-]/)?.[1]; -} - -export function parseContactUsername(contactNameOrUri: string): string | undefined { - // Contact URI format is 'username-rocketchat_userid-random_key@rocketchat_hostname', assigned by the rocket.chat client on the REGISTER request - // Non-rocket.chat sessions will likely have a different format - return contactNameOrUri.match(/^(\d+)\-/)?.[1]; -} - -export function parseEventUsername(eventData: EventData): string | undefined { - const { 'Channel-Name': channelName } = eventData; - - return parseChannelUsername(channelName); -} diff --git a/packages/freeswitch/src/eventParser/parseEventCallId.ts b/packages/freeswitch/src/eventParser/parseEventCallId.ts deleted file mode 100644 index 47be95eda21f8..0000000000000 --- a/packages/freeswitch/src/eventParser/parseEventCallId.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { EventData } from './parseEventData'; - -function shouldUseOtherLegId(eventData: EventData): boolean { - // If the call ID is from a different channel, then it should be correct - if (eventData['Channel-Call-UUID'] !== eventData['Unique-ID']) { - return false; - } - - // If we don't have an originator ID, then we don't have anything to overwrite with - if (eventData['Other-Type'] !== 'originator' || !eventData['Other-Leg-Unique-ID']) { - return false; - } - - // #ToDo: Confirm if these conditions hold up on calls with extra legs (eg. voicemail) - if ( - eventData['Caller-Direction'] !== 'outbound' || - (eventData['Other-Leg-Direction'] === 'outbound' && eventData['Other-Leg-Logical-Direction']) - ) { - return false; - } - - return true; -} - -/** - * Gets the call id from the event data. - * For most cases the call id will be the value that freeswitch sends on 'Channel-Call-UUID', - * but on the callee leg of a call that variable will only have the correct value on events triggered while the call is ongoing - * so for the callee leg we sometimes pick it from a different attribute. - * - * This function doesn't validate if an id was actually received, so it might return undefined, but FreeSwitch SHOULD always be sending one. - */ -export function parseEventCallId(eventData: EventData): string | undefined { - if (shouldUseOtherLegId(eventData)) { - return eventData['Other-Leg-Unique-ID']; - } - - return eventData['Channel-Call-UUID']; -} diff --git a/packages/freeswitch/src/eventParser/parseEventData.ts b/packages/freeswitch/src/eventParser/parseEventData.ts deleted file mode 100644 index 43426bb765086..0000000000000 --- a/packages/freeswitch/src/eventParser/parseEventData.ts +++ /dev/null @@ -1,168 +0,0 @@ -import type { IFreeSwitchChannelEvent } from '@rocket.chat/core-typings'; - -import { filterOutMissingData } from './filterOutMissingData'; -import { filterStringList } from './filterStringList'; -import { parseChannelUsername } from './parseChannelUsername'; -import { parseEventCallId } from './parseEventCallId'; -import { parseEventLeg } from './parseEventLeg'; -import { parseTimestamp } from './parseTimestamp'; -import { logger } from '../logger'; -import { parseEventExtensions } from './parseEventExtensions'; - -export type EventData = Record & Record<`variable_${string}`, string | string[] | undefined>; - -export function parseEventData(eventName: string, eventData: EventData): Omit | undefined { - const { - 'Channel-Name': channelName = '', - 'Channel-State': channelState = '', - 'Channel-Call-State': channelCallState = '', - 'Channel-State-Number': channelStateNumber, - 'Channel-Call-State-Number': channelCallStateNumber, - 'Original-Channel-Call-State': originalChannelCallState, - 'Event-Sequence': sequenceStr, - 'Event-Date-Timestamp': timestamp, - 'Unique-ID': channelUniqueId, - - 'Call-Direction': callDirection, - 'Channel-HIT-Dialplan': channelHitDialplan, - 'Answer-State': answerState, - - 'Hangup-Cause': hangupCause, - - 'Bridge-A-Unique-ID': bridgeA, - 'Bridge-B-Unique-ID': bridgeB, - 'Bridged-To': bridgedTo, - - 'Presence-Call-Direction': presenceCallDirection, - 'Channel-Presence-ID': channelPresenceId, - - 'Channel-Read-Codec-Name': codecReadName, - 'Channel-Read-Codec-Rate': codecReadRate, - 'Channel-Write-Codec-Name': codecWriteName, - 'Channel-Write-Codec-Rate': codecWriteRate, - - ...rawEventData - } = eventData; - - if (!channelUniqueId || !sequenceStr) { - logger.error({ msg: 'Channel Event is missing either the Unique-ID or Event-Sequence', eventData }); - return; - } - - const sequence = parseInt(sequenceStr); - if (!sequence || typeof sequence !== 'number' || !Number.isInteger(sequence)) { - logger.error({ msg: 'Failed to parse Event-Sequence', eventData }); - return; - } - - const callUniqueId = parseEventCallId(eventData) || channelUniqueId; - const channelUsername = parseChannelUsername(channelName); - const firedAt = parseTimestamp(timestamp) || new Date(); - - const callerLeg = parseEventLeg('Caller', eventData); - const otherLeg = parseEventLeg('Other-Leg', eventData); - const bridgeUniqueIds = [bridgeA, bridgeB].filter((bridgeId) => bridgeId) as string[]; - - const legs: IFreeSwitchChannelEvent['legs'] = { - ...(callerLeg?.uniqueId && { [callerLeg.uniqueId]: callerLeg }), - ...(otherLeg?.uniqueId && { [otherLeg.uniqueId]: otherLeg }), - }; - - const variables = filterStringList( - eventData, - (key) => key.startsWith('variable_'), - ([key, value]) => { - return [key.replace('variable_', ''), value || '']; - }, - ) as Record; - const metadata = filterStringList(eventData, (key) => isMetadata(key)) as Record; - const unusedRawData = filterStringList(rawEventData, (key) => { - if (isMetadata(key)) { - return false; - } - if (key.startsWith('variable_')) { - return false; - } - - for (const { legName } of Object.values(legs)) { - if (key.startsWith(`${legName}-`)) { - return false; - } - } - - if (otherLeg && key === 'Other-Type') { - return false; - } - - if (key === 'Channel-Call-UUID') { - return rawEventData['Channel-Call-UUID'] !== callUniqueId; - } - - return true; - }) as Record; - - const event: Omit = { - channelUniqueId, - eventName, - sequence, - firedAt, - receivedAt: new Date(), - callUniqueId, - channelName, - channelState, - channelStateNumber, - channelCallStateNumber, - channelCallState, - - originalChannelCallState, - channelUsername, - answerState, - callDirection, - channelHitDialplan, - hangupCause, - - ...(bridgeUniqueIds.length && { bridgeUniqueIds }), - bridgedTo, - legs, - metadata: filterOutMissingData(metadata), - ...(Object.keys(variables).length && { variables }), - raw: filterOutMissingData(unusedRawData), - - codecs: { - ...{ - read: { - ...{ - name: codecReadName, - rate: codecReadRate, - }, - }, - write: { - ...{ - nme: codecWriteName, - rate: codecWriteRate, - }, - }, - }, - }, - - presenceCallDirection, - channelPresenceId, - }; - - const filteredEvent = { - ...filterOutMissingData(event), - channelName, - channelCallState, - channelState, - }; - const extensions = parseEventExtensions(filteredEvent); - - return { - ...filteredEvent, - ...extensions, - }; -} - -function isMetadata(key: string): boolean { - return key.startsWith('Event-') || key.startsWith('FreeSWITCH-') || key.startsWith('Core-'); -} diff --git a/packages/freeswitch/src/eventParser/parseEventExtensions.ts b/packages/freeswitch/src/eventParser/parseEventExtensions.ts deleted file mode 100644 index 5516c20073885..0000000000000 --- a/packages/freeswitch/src/eventParser/parseEventExtensions.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { AtLeast, IFreeSwitchChannelEvent, IFreeSwitchChannelEventLeg } from '@rocket.chat/core-typings'; - -import { parseChannelUsername, parseContactUsername } from './parseChannelUsername'; - -function normalizeUsername(value?: string): string | undefined { - if (!value) { - return undefined; - } - - if (value.startsWith('sofia/internal') || value.startsWith('sofia/external')) { - return parseChannelUsername(value); - } - - if (value.match(/^\d+$/)) { - return value; - } - - return parseContactUsername(value); -} - -function getMostLikelyUsername(valueList: (string | undefined)[]): string | undefined { - const parsedValues: string[] = []; - - for (const value of valueList) { - const parsedValue = normalizeUsername(value); - if (!parsedValue) { - continue; - } - - if (parsedValue === value) { - return parsedValue; - } - - parsedValues.push(parsedValue); - } - - return parsedValues.shift(); -} - -function getOriginatorLeg( - event: Omit, -): AtLeast | undefined { - const legs = event.legs && Object.values(event.legs); - if (!legs?.length) { - return undefined; - } - - const selfLeg = event.legs[event.channelUniqueId]; - const originator = legs.find((leg) => leg.type === 'originator'); - - if (event.callDirection === 'inbound') { - return originator || selfLeg; - } - - if (originator) { - return originator; - } - - const originatee = legs.find((leg) => leg.type === 'originatee'); - if (originatee && selfLeg && selfLeg.type !== 'originatee') { - return selfLeg; - } - - return undefined; -} - -export function parseEventExtensions( - event: Omit, -): { caller?: string; callee?: string } | undefined { - const legs = event.legs && Object.values(event.legs); - const selfLeg = event.legs?.[event.channelUniqueId]; - - const allDestinationNumbers = legs?.map(({ destinationNumber }) => destinationNumber) || []; - const allCallerNumbers = legs?.map(({ callerNumber }) => callerNumber) || []; - - // The dialed_extension variable is only available in a few specific events, but when it's there, it's ALWAYS right. - // It won't ever be an array, but just to be type-safe - const dialedExtension = Array.isArray(event.variables?.dialed_extension) - ? event.variables.dialed_extension.shift() - : event.variables?.dialed_extension; - - const originator = getOriginatorLeg(event); - if (event.callDirection === 'outbound' && originator) { - // If we have an originator, use it as the source of truth - return { - caller: getMostLikelyUsername([originator.channelName]), - callee: getMostLikelyUsername([dialedExtension, originator.destinationNumber]), - }; - } - - // If the channel is inbound, then it has never received any call, only initiated. - if (event.callDirection === 'inbound') { - // The username of every leg is always the original caller - const anyUsername = legs - ?.map(({ username }) => username) - .filter((username) => username) - .pop(); - - return { - caller: getMostLikelyUsername([event.channelUsername, selfLeg?.username, anyUsername]), - // Callee might not be available at all if the state is still CS_NEW - callee: getMostLikelyUsername([dialedExtension, selfLeg?.destinationNumber, ...allDestinationNumbers]), - }; - } - - // Caller-Number and Destination-Number always have some sort of identification of the right caller/destination - // For rocket.chat internal calls, we'll always be able to parse it into an extension number - // For external calls, this might not be identifying the extension at all. - return { - caller: getMostLikelyUsername([...allCallerNumbers]), - callee: getMostLikelyUsername([dialedExtension, ...allDestinationNumbers]), - }; -} diff --git a/packages/freeswitch/src/eventParser/parseEventLeg.ts b/packages/freeswitch/src/eventParser/parseEventLeg.ts deleted file mode 100644 index bc7210740df30..0000000000000 --- a/packages/freeswitch/src/eventParser/parseEventLeg.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { AtLeast, IFreeSwitchChannelEventLeg, IFreeSwitchChannelEventLegProfile } from '@rocket.chat/core-typings'; - -import { filterOutMissingData } from './filterOutMissingData'; -import { filterStringList } from './filterStringList'; -import type { EventData } from './parseEventData'; -import { parseTimestamp } from './parseTimestamp'; - -export function parseEventLeg( - legName: string, - eventData: EventData, -): AtLeast | undefined { - const legData = filterStringList( - eventData, - (key) => key.startsWith(`${legName}-`), - ([key, value]) => { - return [key.replace(`${legName}-`, ''), value]; - }, - ) as Record; - - const legType = legName === 'Other-Leg' ? eventData['Other-Type'] : undefined; - - const { - 'Direction': direction, - 'Logical-Direction': logicalDirection, - 'Username': username, - 'Caller-ID-Name': callerName, - 'Caller-ID-Number': callerNumber, - 'Orig-Caller-ID-Name': originalCallerName, - 'Orig-Caller-ID-Number': originalCallerNumber, - 'Callee-ID-Name': calleeName, - 'Callee-ID-Number': calleeNumber, - 'Network-Addr': networkAddress, - 'Destination-Number': destinationNumber, - 'Unique-ID': uniqueId, - 'Source': source, - 'Context': context, - 'Channel-Name': channelName, - - 'Dialplan': dialplan, - 'Profile-Index': profileIndex, - 'ANI': ani, - 'RDNIS': rdnis, - 'Transfer-Source': transferSource, - 'Screen-Bit': screenBit, - 'Privacy-Hide-Name': privacyHideName, - 'Privacy-Hide-Number': privacyHideNumber, - - 'Profile-Created-Time': profileCreatedTime, - 'Channel-Created-Time': channelCreatedTime, - 'Channel-Answered-Time': channelAnsweredTime, - 'Channel-Progress-Time': channelProgressTime, - 'Channel-Bridged-Time': channelBridgedTime, - 'Channel-Progress-Media-Time': channelProgressMediaTime, - 'Channel-Hangup-Time': channelHangupTime, - 'Channel-Transfer-Time': channelTransferTime, - 'Channel-Resurrect-Time': channelRessurectTime, - 'Channel-Last-Hold': channelLastHold, - ...rawLegData - } = legData; - - if (!uniqueId) { - return; - } - - const profile: IFreeSwitchChannelEventLegProfile = { - ...filterOutMissingData({ - profileIndex, - profileCreatedTime: parseTimestamp(profileCreatedTime), - channelCreatedTime: parseTimestamp(channelCreatedTime), - channelAnsweredTime: parseTimestamp(channelAnsweredTime), - channelProgressTime: parseTimestamp(channelProgressTime), - channelBridgedTime: parseTimestamp(channelBridgedTime), - channelProgressMediaTime: parseTimestamp(channelProgressMediaTime), - channelHangupTime: parseTimestamp(channelHangupTime), - channelTransferTime: parseTimestamp(channelTransferTime), - channelRessurectTime: parseTimestamp(channelRessurectTime), - channelLastHold: parseTimestamp(channelLastHold), - }), - }; - - const effectiveProfileIndex = profileIndex || '1'; - - const leg: AtLeast = { - legName, - uniqueId, - type: legType, - direction, - logicalDirection, - username, - callerName, - callerNumber, - originalCallerName, - originalCallerNumber, - calleeName, - calleeNumber, - networkAddress, - destinationNumber, - source, - context, - channelName, - transferSource, - - // If there's no profileIndex, default to '1', but do not save a profile if there's nothing in it - ...(Object.keys(profile).length > 0 && { profiles: { [effectiveProfileIndex]: { ...profile, profileIndex: effectiveProfileIndex } } }), - - dialplan, - ani, - rdnis, - screenBit, - privacyHideName, - privacyHideNumber, - - raw: filterOutMissingData(rawLegData), - }; - - return filterOutMissingData(leg); -} diff --git a/packages/freeswitch/src/eventParser/parseTimestamp.ts b/packages/freeswitch/src/eventParser/parseTimestamp.ts deleted file mode 100644 index 374fd5b5a0057..0000000000000 --- a/packages/freeswitch/src/eventParser/parseTimestamp.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function parseTimestamp(timestamp: string | undefined): Date | undefined { - if (!timestamp || timestamp === '0') { - return undefined; - } - - const value = parseInt(timestamp); - if (Number.isNaN(value) || value < 0) { - return undefined; - } - - const timeValue = Math.floor(value / 1000); - return new Date(timeValue); -} diff --git a/packages/freeswitch/src/index.ts b/packages/freeswitch/src/index.ts deleted file mode 100644 index cfd70023535cc..0000000000000 --- a/packages/freeswitch/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './commands'; -export * from './logger'; -export * from './eventParser/parseEventData'; -export * from './eventParser/computeChannelFromEvents'; -export * from './esl'; -export * from './FreeSwitchOptions'; diff --git a/packages/freeswitch/src/logger.ts b/packages/freeswitch/src/logger.ts deleted file mode 100644 index 4c025069d8ad4..0000000000000 --- a/packages/freeswitch/src/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Logger } from '@rocket.chat/logger'; - -export const logger = new Logger('FreeSwitch'); diff --git a/packages/freeswitch/src/utils/mapUserData.ts b/packages/freeswitch/src/utils/mapUserData.ts deleted file mode 100644 index 265cf1f6946eb..0000000000000 --- a/packages/freeswitch/src/utils/mapUserData.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; -import type { StringMap } from 'esl'; - -import { parseUserStatus } from './parseUserStatus'; - -export function mapUserData(user: StringMap): FreeSwitchExtension { - const { - userid: extension, - context, - domain, - groups, - contact, - callgroup: callGroup, - effective_caller_id_name: callerName, - effective_caller_id_number: callerNumber, - } = user; - - if (!extension) { - throw new Error('Invalid user identification.'); - } - - return { - extension, - context, - domain, - groups: groups?.split('|') || [], - status: parseUserStatus(contact), - contact, - callGroup, - callerName, - callerNumber, - }; -} diff --git a/packages/freeswitch/src/utils/parseUserList.ts b/packages/freeswitch/src/utils/parseUserList.ts deleted file mode 100644 index 21ed42ef1ac20..0000000000000 --- a/packages/freeswitch/src/utils/parseUserList.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { StringMap } from 'esl'; - -export function parseUserList(commandResponse: StringMap): Record[] { - const { _body: text } = commandResponse; - - if (!text || typeof text !== 'string') { - throw new Error('Invalid response from FreeSwitch server.'); - } - - const lines = text.split('\n'); - const columnsLine = lines.shift(); - if (!columnsLine) { - throw new Error('Invalid response from FreeSwitch server.'); - } - - const columns = columnsLine.split('|'); - - const users = new Map>(); - - for (const line of lines) { - const values = line.split('|'); - if (!values.length || !values[0]) { - continue; - } - const user = Object.fromEntries( - values.map((value, index) => { - return [(columns.length > index && columns[index]) || `column${index}`, value]; - }), - ); - - if (!user.userid || user.userid === '+OK') { - continue; - } - - const { group, ...newUserData } = user; - - const existingUser = users.get(user.userid); - const groups = (existingUser?.groups || []) as string[]; - - if (group && !groups.includes(group)) { - groups.push(group); - } - - users.set(user.userid, { - ...(users.get(user.userid) || newUserData), - groups, - }); - } - - return [...users.values()].map((user) => ({ - ...user, - groups: (user.groups as string[]).join('|'), - })); -} diff --git a/packages/freeswitch/src/utils/parseUserStatus.ts b/packages/freeswitch/src/utils/parseUserStatus.ts deleted file mode 100644 index 48cb9b32474a0..0000000000000 --- a/packages/freeswitch/src/utils/parseUserStatus.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; - -export function parseUserStatus(status: string | undefined): FreeSwitchExtension['status'] { - if (!status) { - return 'UNKNOWN'; - } - - if (status === 'error/user_not_registered') { - return 'UNREGISTERED'; - } - - if (status.startsWith('sofia/')) { - return 'REGISTERED'; - } - - return 'UNKNOWN'; -} diff --git a/packages/freeswitch/tests/eventParser/computeChannelFromEvents.test.ts b/packages/freeswitch/tests/eventParser/computeChannelFromEvents.test.ts deleted file mode 100644 index 2ddeb19673cc6..0000000000000 --- a/packages/freeswitch/tests/eventParser/computeChannelFromEvents.test.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import type { IFreeSwitchChannelEvent } from '@rocket.chat/core-typings'; - -import { computeChannelFromEvents } from '../../src/eventParser/computeChannelFromEvents'; - -describe('computeChannelFromEvents', () => { - const createTestEvent = (overrides: Omit): IFreeSwitchChannelEvent => ({ - _id: 'event-123', - _updatedAt: new Date(), - ...overrides, - }); - - it('should compute channel from events', async () => { - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callDirection: 'outbound', - caller: '2001', - callee: '2002', - raw: {}, - legs: { - 'Caller-Leg': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - channelName: 'sofia/internal/1001@192.168.1.100', - destinationNumber: '1002', - raw: {}, - profiles: { - 1: { - profileIndex: '1', - channelCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - profileCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - }, - }, - }, - }, - }), - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_ANSWER', - sequence: 2, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:01.000Z'), - receivedAt: new Date('2024-02-28T12:00:01.100Z'), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelCallState: 'RINGING', - callDirection: 'outbound', - channelUsername: '1001', - raw: {}, - legs: { - 'Caller-Leg': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - channelName: 'sofia/internal/1001@192.168.1.100', - destinationNumber: '1002', - raw: {}, - profiles: { - 1: { - profileIndex: '1', - channelCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - profileCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - channelAnsweredTime: new Date('2024-02-28T12:00:01.000Z'), - }, - }, - }, - }, - }), - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_BRIDGE', - sequence: 3, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:02.000Z'), - receivedAt: new Date('2024-02-28T12:00:02.100Z'), - bridgeUniqueIds: ['channel-123', 'channel-456'], - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelCallState: 'ACTIVE', - raw: {}, - legs: {}, - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - name: 'sofia/internal/1001@192.168.1.100', - callDirection: 'outbound', - freeSwitchUser: '1001', - callers: ['2001'], - callees: ['2002'], - bridgedTo: ['channel-456'], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date('2024-02-28T12:00:00.000Z'), - kind: 'internal', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_BRIDGE', - sequence: 3, - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelCallState: 'ACTIVE', - callDirection: 'outbound', - bridgeUniqueIds: ['channel-123', 'channel-456'], - legs: { - 'Caller-Leg': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - channelName: 'sofia/internal/1001@192.168.1.100', - destinationNumber: '1002', - profiles: { - 1: { - profileIndex: '1', - channelCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - profileCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - channelAnsweredTime: new Date('2024-02-28T12:00:01.000Z'), - caller: '2001', - callee: '2002', - }, - }, - }, - }, - channelUsername: '1001', - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - caller: '2001', - callee: '2002', - answerState: undefined, - originalChannelCallState: undefined, - }, - { - eventName: 'CHANNEL_ANSWER', - sequence: 2, - firedAt: new Date('2024-02-28T12:00:01.000Z'), - receivedAt: new Date('2024-02-28T12:00:01.100Z'), - channelState: 'CS_EXECUTE', - channelCallState: 'RINGING', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - { - eventName: 'CHANNEL_BRIDGE', - sequence: 3, - firedAt: new Date('2024-02-28T12:00:02.000Z'), - receivedAt: new Date('2024-02-28T12:00:02.100Z'), - channelState: 'CS_EXECUTE', - channelCallState: 'ACTIVE', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callee: '2002', - caller: '2001', - channelCallState: 'DOWN', - channelState: 'CS_NEW', - newValues: { - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callDirection: 'outbound', - legs: { - 'Caller-Leg': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - profiles: { - 1: { - profileIndex: '1', - channelCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - profileCreatedTime: new Date('2024-02-28T12:00:00.000Z'), - caller: '2001', - callee: '2002', - }, - }, - username: '1001', - channelName: 'sofia/internal/1001@192.168.1.100', - destinationNumber: '1002', - }, - }, - }, - }, - { - eventName: 'CHANNEL_ANSWER', - sequence: 2, - firedAt: new Date('2024-02-28T12:00:01.000Z'), - receivedAt: new Date('2024-02-28T12:00:01.100Z'), - channelCallState: 'RINGING', - channelState: 'CS_EXECUTE', - newValues: { - channelUsername: '1001', - legs: { - 'Caller-Leg': { - profiles: { - 1: { - channelAnsweredTime: new Date('2024-02-28T12:00:01.000Z'), - }, - }, - }, - }, - }, - modifiedValues: { - channelState: { oldValue: 'CS_NEW', newValue: 'CS_EXECUTE' }, - channelCallState: { oldValue: 'DOWN', newValue: 'RINGING' }, - }, - }, - { - eventName: 'CHANNEL_BRIDGE', - sequence: 3, - firedAt: new Date('2024-02-28T12:00:02.000Z'), - receivedAt: new Date('2024-02-28T12:00:02.100Z'), - channelCallState: 'ACTIVE', - channelState: 'CS_EXECUTE', - newValues: { - bridgeUniqueIds: ['channel-123', 'channel-456'], - }, - modifiedValues: { - channelCallState: { oldValue: 'RINGING', newValue: 'ACTIVE' }, - }, - }, - ], - }); - }); - - it('should handle missing legs', async () => { - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - raw: {}, - legs: {}, - bridgedTo: 'channel-456', - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - name: 'sofia/internal/1001@192.168.1.100', - callDirection: '', - callers: [], - callees: [], - bridgedTo: ['channel-456'], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date('2024-02-28T12:00:00.000Z'), - kind: 'internal', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - bridgedTo: 'channel-456', - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelCallState: 'DOWN', - channelState: 'CS_NEW', - newValues: { - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - bridgedTo: 'channel-456', - }, - }, - ], - }); - }); - - it('should handle missing caller leg', async () => { - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callUniqueId: 'call-123', - channelName: '', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - raw: {}, - legs: { - 'Other-Leg': { - legName: 'Other-Leg', - uniqueId: 'channel-456', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1002', - channelName: 'sofia/internal/1002@192.168.1.101', - destinationNumber: '1001', - }, - }, - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - callDirection: '', - callers: [], - callees: [], - bridgedTo: [], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date('2024-02-28T12:00:00.000Z'), - kind: 'unknown', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - legs: { - 'Other-Leg': { - legName: 'Other-Leg', - uniqueId: 'channel-456', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1002', - channelName: 'sofia/internal/1002@192.168.1.101', - destinationNumber: '1001', - }, - }, - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - newValues: { - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - legs: { - 'Other-Leg': { - legName: 'Other-Leg', - uniqueId: 'channel-456', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1002', - channelName: 'sofia/internal/1002@192.168.1.101', - destinationNumber: '1001', - }, - }, - }, - }, - ], - }); - }); - - it('should handle missing call ID', async () => { - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callUniqueId: '', - channelName: '', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - raw: {}, - legs: {}, - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - callDirection: '', - callers: [], - callees: [], - bridgedTo: [], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date('2024-02-28T12:00:00.000Z'), - kind: 'unknown', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - channelState: 'CS_NEW', - channelCallState: 'DOWN', - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.000Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - newValues: { channelState: 'CS_NEW', channelCallState: 'DOWN' }, - }, - ], - }); - }); - - it('should handle missing event timestamps', async () => { - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt: new Date('2024-02-28T12:00:00.100Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - raw: {}, - legs: {}, - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - callDirection: '', - callers: [], - callees: [], - bridgedTo: [], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date('2024-02-28T12:00:00.100Z'), - kind: 'internal', - name: 'sofia/internal/1007@host', - freeSwitchUser: '1007', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.100Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date('2024-02-28T12:00:00.100Z'), - receivedAt: new Date('2024-02-28T12:00:00.100Z'), - channelState: 'CS_NEW', - channelCallState: 'DOWN', - newValues: { - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - }, - }, - ], - }); - }); - - it('validate fallbacks for unlikely scenarios', async () => { - // Force missing - const firedAt = undefined as unknown as Date; - const receivedAt = undefined as unknown as Date; - - const events = [ - createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - metadata: {}, - firedAt, - receivedAt, - callUniqueId: 'call-123', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - raw: {}, - legs: {}, - }), - ]; - - const result = await computeChannelFromEvents(events); - - expect(result).toEqual({ - channel: { - uniqueId: 'channel-123', - callDirection: '', - callers: [], - callees: [], - bridgedTo: [], - profiles: [], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - kind: 'internal', - name: 'sofia/internal/1007@host', - freeSwitchUser: '1007', - finalState: { - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - }, - events: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt, - receivedAt, - channelState: 'CS_NEW', - channelCallState: 'DOWN', - callee: undefined, - caller: undefined, - answerState: undefined, - originalChannelCallState: undefined, - }, - ], - }, - deltas: [ - { - eventName: 'CHANNEL_CREATE', - sequence: 1, - channelState: 'CS_NEW', - channelCallState: 'DOWN', - newValues: { - callUniqueId: 'call-123', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - channelName: 'sofia/internal/1007@host', - channelUsername: '1007', - }, - }, - ], - }); - }); - - it('should return undefined for empty events array', async () => { - const result = await computeChannelFromEvents([]); - - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/computeChannelProfiles.test.ts b/packages/freeswitch/tests/eventParser/computeChannelProfiles.test.ts deleted file mode 100644 index 571dd0df6b05f..0000000000000 --- a/packages/freeswitch/tests/eventParser/computeChannelProfiles.test.ts +++ /dev/null @@ -1,385 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import type { IFreeSwitchChannelEventLegProfile } from '@rocket.chat/core-typings'; - -import { computeChannelProfiles } from '../../src/eventParser/computeChannelProfiles'; - -describe('computeChannelProfiles', () => { - const createTestProfile = (overrides: Partial): IFreeSwitchChannelEventLegProfile => ({ - ...overrides, - }); - - it('should compute channel profiles with all timestamps', () => { - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123457789), - channelProgressTime: new Date(1709123457889), - channelProgressMediaTime: new Date(1709123457989), - channelBridgedTime: new Date(1709123458789), - channelHangupTime: new Date(1709123466789), - channelTransferTime: new Date(1709123456791), - channelRessurectTime: new Date(1709123456792), - channelLastHold: new Date(1709123456793), - }), - }; - - const result = computeChannelProfiles(profiles); - - expect(result).toEqual({ - profiles: [ - { - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123457789), - channelProgressTime: new Date(1709123457889), - channelProgressMediaTime: new Date(1709123457989), - channelBridgedTime: new Date(1709123458789), - channelHangupTime: new Date(1709123466789), - channelTransferTime: new Date(1709123456791), - channelRessurectTime: new Date(1709123456792), - channelLastHold: new Date(1709123456793), - callDuration: 8000, - answered: true, - media: true, - bridged: true, - }, - ], - anyMedia: true, - anyAnswer: true, - anyBridge: true, - durationSum: 8000, - totalDuration: 8000, - startedAt: new Date(1709123458789), - }); - }); - - it('should handle missing timestamps', () => { - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '1', - }), - }; - - const result = computeChannelProfiles(profiles); - expect(result).toEqual({ - profiles: [{ profileIndex: '1', bridged: false }], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: undefined, - }); - }); - - it('should handle multiple profiles', () => { - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - }), - profile2: createTestProfile({ - profileIndex: '2', - profileCreatedTime: new Date(1709123456791), - channelCreatedTime: new Date(1709123456792), - }), - }; - - const result = computeChannelProfiles(profiles); - expect(result).toEqual({ - profiles: [ - { - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - nextProfileCreatedTime: new Date(1709123456791), - callDuration: 0, - answered: true, - media: true, - bridged: false, - }, - { - channelCreatedTime: new Date(1709123456792), - profileIndex: '2', - profileCreatedTime: new Date(1709123456791), - callDuration: 0, - answered: true, - media: true, - bridged: false, - }, - ], - anyMedia: true, - anyAnswer: true, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - startedAt: new Date(1709123456789), - }); - }); - - it('should handle multiple full profiles', () => { - const forcedInvalidAttributes = { - unknownAttribute: 'value', - unsetAttribute: false, - } as any; - - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - channelProgressTime: new Date(1709123456889), - channelProgressMediaTime: new Date(1709123456989), - channelBridgedTime: new Date(1709123456789), - channelLastHold: new Date(1709123456789), - }), - profile2: createTestProfile({ - profileIndex: '2', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - channelTransferTime: new Date(1709123656791), - channelRessurectTime: new Date(1709123656792), - channelLastHold: new Date(1709123456789), - ...forcedInvalidAttributes, - }), - }; - - const result = computeChannelProfiles(profiles); - expect(result).toEqual({ - profiles: [ - { - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - channelProgressTime: new Date(1709123456889), - channelProgressMediaTime: new Date(1709123456989), - channelBridgedTime: new Date(1709123456789), - channelLastHold: new Date(1709123456789), - callDuration: 200000, - answered: true, - media: true, - bridged: true, - nextProfileCreatedTime: new Date(1709123656789), - }, - { - profileIndex: '2', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - channelTransferTime: new Date(1709123656791), - channelRessurectTime: new Date(1709123656792), - // should not be carried over due to being lower than the profile create time - channelLastHold: undefined, - callDuration: 8000, - answered: true, - media: true, - bridged: true, - }, - ], - anyMedia: true, - anyAnswer: true, - anyBridge: true, - durationSum: 208000, - totalDuration: 210000, - startedAt: new Date(1709123456789), - }); - }); - - it('should handle empty profiles object', () => { - const profiles: Record = {}; - - const result = computeChannelProfiles(profiles); - expect(result).toEqual({ profiles: [], anyMedia: false, anyAnswer: false, anyBridge: false, durationSum: 0, totalDuration: 0 }); - }); - - it('should handle profiles with no creation time', () => { - const profiles: Record = { - profile2: createTestProfile({ - profileIndex: '2', - channelAnsweredTime: new Date(1709123457789), - }), - }; - - const result = computeChannelProfiles(profiles); - expect(result).toEqual({ - profiles: [{ profileIndex: '2', bridged: false }], - anyMedia: false, - anyAnswer: false, - anyBridge: false, - durationSum: 0, - totalDuration: 0, - }); - }); - - it('validate fallback for unlikely data', () => { - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '2', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - }), - profile3: createTestProfile({ - profileIndex: '1', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - }), - profile2: createTestProfile({ - profileIndex: '1', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - }), - }; - - const result = computeChannelProfiles(profiles); - - expect(result).toEqual({ - profiles: [ - { - profileIndex: '1', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - callDuration: 0, - answered: true, - media: true, - bridged: false, - }, - { - profileIndex: '1', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - nextProfileCreatedTime: new Date(1709123656789), - callDuration: 0, - answered: true, - media: true, - bridged: false, - }, - { - profileIndex: '2', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - callDuration: 8000, - answered: true, - media: true, - bridged: true, - }, - ], - anyMedia: true, - anyAnswer: true, - anyBridge: true, - durationSum: 8000, - totalDuration: 8000, - startedAt: new Date(1709123658789), - }); - }); - - it('should handle a mix of profiles with and without creation time', () => { - const profiles: Record = { - profile1: createTestProfile({ - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - channelProgressTime: new Date(1709123456889), - channelProgressMediaTime: new Date(1709123456989), - channelBridgedTime: new Date(1709123456789), - channelLastHold: new Date(1709123456789), - }), - profile2: createTestProfile({ - profileIndex: '2', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123656785), - }), - profile3: createTestProfile({ - profileIndex: '3', - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123656785), - }), - profile4: createTestProfile({ - profileIndex: '4', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - channelTransferTime: new Date(1709123656791), - channelRessurectTime: new Date(1709123656792), - channelLastHold: new Date(1709123456789), - }), - }; - - const result = computeChannelProfiles(profiles); - - expect(result).toEqual({ - profiles: [ - { - profileIndex: '1', - profileCreatedTime: new Date(1709123456789), - channelCreatedTime: new Date(1709123456790), - channelAnsweredTime: new Date(1709123456789), - channelProgressTime: new Date(1709123456889), - channelProgressMediaTime: new Date(1709123456989), - channelBridgedTime: new Date(1709123456789), - channelLastHold: new Date(1709123456789), - nextProfileCreatedTime: new Date(1709123656789), - callDuration: 200000, - answered: true, - media: true, - bridged: true, - }, - { - profileIndex: '4', - profileCreatedTime: new Date(1709123656789), - channelAnsweredTime: new Date(1709123657789), - channelProgressTime: new Date(1709123657889), - channelProgressMediaTime: new Date(1709123657989), - channelBridgedTime: new Date(1709123658789), - channelHangupTime: new Date(1709123666789), - channelTransferTime: new Date(1709123656791), - channelRessurectTime: new Date(1709123656792), - callDuration: 8000, - answered: true, - media: true, - bridged: true, - }, - { - channelCreatedTime: new Date(1709123456790), - profileIndex: '2', - bridged: false, - }, - { - channelCreatedTime: new Date(1709123456790), - profileIndex: '3', - bridged: false, - }, - ], - anyMedia: true, - anyAnswer: true, - anyBridge: true, - durationSum: 208000, - totalDuration: 210000, - startedAt: new Date(1709123456789), - }); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/extractChannelChangesFromEvent.test.ts b/packages/freeswitch/tests/eventParser/extractChannelChangesFromEvent.test.ts deleted file mode 100644 index bcce540a24700..0000000000000 --- a/packages/freeswitch/tests/eventParser/extractChannelChangesFromEvent.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { extractChannelChangesFromEvent } from '../../src/eventParser/extractChannelChangesFromEvent'; - -describe('extractChannelChangesFromEvent', () => { - it('should detect new values', () => { - const channelState = {}; - const eventValues = { - simple: 'value', - number: 42, - date: new Date('2024-01-01'), - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({ - simple: 'value', - number: 42, - date: new Date('2024-01-01'), - }); - expect(result.changedValues).toEqual({ - simple: 'value', - number: 42, - date: new Date('2024-01-01'), - }); - expect(result.changedExistingValues).toEqual({}); - }); - - it('should detect changed values', () => { - const channelState = { - simple: 'old', - number: 42, - date: new Date('2024-01-01'), - }; - const eventValues = { - simple: 'new', - number: 43, - date: new Date('2024-01-02'), - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({ - simple: 'new', - number: 43, - date: new Date('2024-01-02'), - }); - expect(result.changedExistingValues).toEqual({ - simple: { - oldValue: 'old', - newValue: 'new', - }, - number: { - oldValue: 42, - newValue: 43, - }, - date: { - oldValue: new Date('2024-01-01'), - newValue: new Date('2024-01-02'), - delta: 24 * 60 * 60 * 1000, // 1 day in milliseconds - }, - }); - }); - - it('should handle arrays', () => { - const channelState = { - simple: ['a', 'b'], - variables: ['x', 'y'], - }; - const eventValues = { - simple: ['b', 'c'], - variables: ['y', 'z'], - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({ - simple: ['b', 'c'], - variables: ['y', 'z'], - }); - expect(result.changedExistingValues).toEqual({ - simple: { - oldValue: ['a', 'b'], - newValue: ['b', 'c'], - }, - variables: { - newValue: ['y', 'z'], - oldValue: ['x', 'y'], - }, - }); - }); - - it('should handle bridgeUniqueIds specially', () => { - const channelState = { - bridgeUniqueIds: ['bridge1', 'bridge2'], - }; - const eventValues = { - bridgeUniqueIds: ['bridge2', 'bridge3'], - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({ - bridgeUniqueIds: ['bridge1', 'bridge2', 'bridge3'], - }); - expect(result.changedExistingValues).toEqual({ - bridgeUniqueIds: { - oldValue: ['bridge1', 'bridge2'], - newValue: ['bridge1', 'bridge2', 'bridge3'], - }, - }); - }); - - it('should handle single bridgeUniqueId values', () => { - const channelState = { - bridgeUniqueIds: 'bridge1', - }; - const eventValues = { - bridgeUniqueIds: 'bridge2', - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({ - bridgeUniqueIds: ['bridge1', 'bridge2'], - }); - expect(result.changedExistingValues).toEqual({ - bridgeUniqueIds: { - oldValue: 'bridge1', - newValue: ['bridge1', 'bridge2'], - }, - }); - }); - - it('should ignore unchanged values', () => { - const channelState = { - 'simple': 'value', - 'number': 42, - 'date': new Date('2024-01-01'), - 'variables.array': ['a', 'b'], - }; - const eventValues = { - 'simple': 'value', - 'number': 42, - 'date': new Date('2024-01-01'), - 'variables.array': ['a', 'b'], - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({}); - expect(result.changedExistingValues).toEqual({}); - }); - - it('should ignore missing values in the bridgeUniqueIds param', () => { - const channelState = { - simple: 'value', - bridgeUniqueIds: ['1', '2', '3'], - }; - const eventValues = { - simple: 'value', - bridgeUniqueIds: ['1', '3'], - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({}); - expect(result.changedExistingValues).toEqual({}); - }); - - it('should handle mixed arrays and values', () => { - const channelState = { - 'variables.array': ['1', '3'], - }; - const eventValues = { - 'variables.array': '1', - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({ - 'variables.array': '1', - }); - expect(result.changedExistingValues).toEqual({ - 'variables.array': { - newValue: '1', - oldValue: ['1', '3'], - }, - }); - }); - - it('should handle undefined values', () => { - const channelState = { - simple: 'value', - number: 42, - }; - const eventValues = { - simple: undefined, - number: 42, - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({}); - expect(result.changedExistingValues).toEqual({}); - }); - - it('should handle variables arrays specially', () => { - const channelState = { - 'variables.array': ['a', 'b'], - }; - const eventValues = { - 'variables.array': ['b', 'a'], - }; - - const result = extractChannelChangesFromEvent(channelState, 'test-event', eventValues); - - expect(result.newValues).toEqual({}); - expect(result.changedValues).toEqual({}); - expect(result.changedExistingValues).toEqual({}); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/filterOutMissingData.test.ts b/packages/freeswitch/tests/eventParser/filterOutMissingData.test.ts deleted file mode 100644 index 0e8bc70107480..0000000000000 --- a/packages/freeswitch/tests/eventParser/filterOutMissingData.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { filterOutMissingData } from '../../src/eventParser/filterOutMissingData'; - -describe('filterOutMissingData', () => { - test.each([ - ['case-1', { a: '1', b: '', c: '0', d: undefined }, { a: '1' }], - ['case-2', { a: true, b: false, c: '0' }, { a: true, b: false }], - ['case-3', { a: { b: {} }, c: { d: '1' } }, { c: { d: '1' } }], - ['case-4', { a: [], b: [1], c: {} }, { a: [], b: [1] }], - ['case-5', { a: new Date(), b: {} }, { a: expect.any(Date) }], - ['case-6', {}, {}], - ])('should filter out missing data for test %# (%s)', (_caseId, input, expected) => { - expect(filterOutMissingData(input)).toEqual(expected); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/filterStringList.test.ts b/packages/freeswitch/tests/eventParser/filterStringList.test.ts deleted file mode 100644 index 80fc47a5b0758..0000000000000 --- a/packages/freeswitch/tests/eventParser/filterStringList.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { filterStringList } from '../../src/eventParser/filterStringList'; -import type { EventData } from '../../src/eventParser/parseEventData'; - -function createEventData(data: Record): EventData { - return data as unknown as EventData; -} - -describe('filterStringList', () => { - it('should filter entries based on key filter function', () => { - const eventData = createEventData({ - 'Channel-Name': 'test-channel', - 'Channel-State': 'CS_INIT', - 'variable_test': 'test-value', - 'variable_array': ['value1', 'value2'], - }); - - const result = filterStringList(eventData, (key) => key.startsWith('variable_')); - - expect(result).toEqual({ - variable_test: 'test-value', - variable_array: ['value1', 'value2'], - }); - }); - - it('should return empty object when no entries match filter', () => { - const eventData = createEventData({ - 'Channel-Name': 'test-channel', - 'Channel-State': 'CS_INIT', - }); - - const result = filterStringList(eventData, (key) => key.startsWith('variable_')); - - expect(result).toEqual({}); - }); - - it('should apply map function to filtered entries', () => { - const eventData = createEventData({ - variable_test1: 'value1', - variable_test2: 'value2', - variable_test3: 'value3', - }); - - const result = filterStringList( - eventData, - (key) => key.startsWith('variable_'), - ([key, value]) => [key.replace('variable_', ''), value], - ); - - expect(result).toEqual({ - test1: 'value1', - test2: 'value2', - test3: 'value3', - }); - }); - - it('should filter out undefined mapped entries', () => { - const eventData = createEventData({ - variable_test1: 'value1', - variable_test2: 'value2', - variable_test3: 'value3', - }); - - const result = filterStringList( - eventData, - (key) => key.startsWith('variable_'), - ([key, value]) => (key === 'variable_test2' ? undefined : [key.replace('variable_', ''), value]), - ); - - expect(result).toEqual({ - test1: 'value1', - test3: 'value3', - }); - }); - - it('should handle array values', () => { - const eventData = createEventData({ - variable_array1: ['value1', 'value2'], - variable_array2: ['value3', 'value4'], - }); - - const result = filterStringList( - eventData, - (key) => key.startsWith('variable_'), - ([key, value]) => [key.replace('variable_', ''), value], - ); - - expect(result).toEqual({ - array1: ['value1', 'value2'], - array2: ['value3', 'value4'], - }); - }); - - it('should handle undefined values', () => { - const eventData = createEventData({ - variable_test1: undefined, - variable_test2: 'value2', - }); - - const result = filterStringList( - eventData, - (key) => key.startsWith('variable_'), - ([key, value]) => [key.replace('variable_', ''), value], - ); - - expect(result).toEqual({ - test1: undefined, - test2: 'value2', - }); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/insertDataIntoEventProfile.test.ts b/packages/freeswitch/tests/eventParser/insertDataIntoEventProfile.test.ts deleted file mode 100644 index c8aeed7df47c6..0000000000000 --- a/packages/freeswitch/tests/eventParser/insertDataIntoEventProfile.test.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { insertDataIntoEventProfile } from '../../src/eventParser/insertDataIntoEventProfile'; - -describe('insertDataIntoEventProfile', () => { - const baseEventData = { - callUniqueId: 'test-call-123', - channelName: 'test-channel', - channelState: 'CS_EXECUTE', - channelCallState: 'CS_EXECUTE', - raw: {}, - }; - - it('should handle simple object', () => { - const eventData: any = { - simple: '15', - }; - - const result = insertDataIntoEventProfile(eventData, {}); - - expect(result).toEqual({ - simple: '15', - }); - }); - - it('should handle simple object with legs', () => { - const eventData: any = { - simple: '15', - legs: { - one: { - simple: '20', - }, - }, - }; - - const result = insertDataIntoEventProfile(eventData, {}); - - expect(result).toEqual({ - simple: '15', - legs: { - one: { - simple: '20', - }, - }, - }); - }); - - it('should handle simple object with legs and profiles', () => { - const channelUniqueId = 'test-channel-123'; - const eventData: any = { - simple: '15', - legs: { - one: { - profiles: { - simple: '30', - }, - simple: '20', - }, - [channelUniqueId]: { - profiles: '40', - }, - }, - }; - - const result = insertDataIntoEventProfile(eventData, {}); - - expect(result).toEqual({ - simple: '15', - legs: { - one: { - profiles: { - simple: '30', - }, - simple: '20', - }, - [channelUniqueId]: { - profiles: '40', - }, - }, - }); - }); - - it('should not break when handling invalid data', () => { - const channelUniqueId = 'test-channel-123'; - const date = new Date(); - - const eventData: any = { - legs: { - [channelUniqueId]: { - profiles: date, - }, - }, - }; - - const result = insertDataIntoEventProfile(eventData, {}); - expect(result).toEqual({ - legs: { - [channelUniqueId]: { - profiles: date, - }, - }, - }); - }); - - it('should not break when handling invalid data [2]', () => { - const channelUniqueId = 'test-channel-123'; - const eventData: any = { - legs: { - [channelUniqueId]: { - profiles: { - first: false, - last: 0, - }, - }, - }, - }; - - const result = insertDataIntoEventProfile(eventData, { callee: '20' }); - expect(result).toEqual({ - legs: { - [channelUniqueId]: { - profiles: { - first: false, - last: 0, - }, - }, - }, - }); - }); - - it('should convert event data into paths with basic structure', () => { - const channelUniqueId = 'test-channel-123'; - const eventData: any = { - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - profiles: { - 'profile-1': { - bridgedTo: 'original-bridge', - callee: 'original-callee', - }, - }, - }, - }, - }; - - const dataToInsertIntoProfile = { - bridgedTo: 'new-bridge', - callee: 'new-callee', - }; - - const result = insertDataIntoEventProfile(eventData, dataToInsertIntoProfile); - - expect(result).toEqual({ - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - profiles: { - 'profile-1': { - bridgedTo: 'new-bridge', - callee: 'new-callee', - }, - }, - }, - }, - }); - }); - - it('should handle event data without legs', () => { - const eventData: any = { - ...baseEventData, - }; - const dataToInsertIntoProfile = { - bridgedTo: 'new-bridge', - }; - - const result = insertDataIntoEventProfile(eventData, dataToInsertIntoProfile); - - expect(result).toEqual({ - ...baseEventData, - }); - }); - - it('should handle event data with legs but no profiles', () => { - const channelUniqueId = 'test-channel-123'; - const eventData: any = { - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - }, - }, - }; - const dataToInsertIntoProfile = { - bridgedTo: 'new-bridge', - }; - - const result = insertDataIntoEventProfile(eventData, dataToInsertIntoProfile); - - expect(result).toEqual({ - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - }, - }, - }); - }); - - it('should handle event data with multiple legs', () => { - const channelUniqueId = 'test-channel-123'; - const otherChannelId = 'other-channel-456'; - const eventData: any = { - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - profiles: { - 'profile-1': { - profileIndex: 'profile-1', - }, - }, - }, - [otherChannelId]: { - raw: {}, - legName: `leg-${otherChannelId}`, - uniqueId: otherChannelId, - profiles: { - 'profile-2': { - profileIndex: 'profile-2', - }, - }, - }, - }, - }; - - const dataToInsertIntoProfile = { - bridgedTo: 'new-bridge', - }; - - const result = insertDataIntoEventProfile(eventData, dataToInsertIntoProfile); - - expect(result).toEqual({ - ...baseEventData, - legs: { - [channelUniqueId]: { - raw: {}, - legName: `leg-${channelUniqueId}`, - uniqueId: channelUniqueId, - profiles: { - 'profile-1': { - profileIndex: 'profile-1', - bridgedTo: 'new-bridge', - }, - }, - }, - [otherChannelId]: { - raw: {}, - legName: `leg-${otherChannelId}`, - uniqueId: otherChannelId, - profiles: { - 'profile-2': { - profileIndex: 'profile-2', - bridgedTo: 'new-bridge', - }, - }, - }, - }, - }); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseChannelKind.test.ts b/packages/freeswitch/tests/eventParser/parseChannelKind.test.ts deleted file mode 100644 index 4dfce5960f17e..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseChannelKind.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { parseChannelKind } from '../../src/eventParser/parseChannelKind'; - -describe('parseChannelKind', () => { - it('should parse kind from originator channel', () => { - const channelName = 'sofia/internal/1001@voip.open.rocket.chat:9999'; - const result = parseChannelKind(channelName); - expect(result).toBe('internal'); - }); - - it('should parse kind from external channel', () => { - const channelName = 'sofia/external/1001@voip.open.rocket.chat:9999'; - const result = parseChannelKind(channelName); - expect(result).toBe('external'); - }); - - it('should parse kind from voicemail channel', () => { - const channelName = 'loopback/voicemail-a'; - const result = parseChannelKind(channelName); - expect(result).toBe('voicemail'); - }); - - it('should parse kind from originatee channel', () => { - const channelName = 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-spo254ol@open.rocket.chat'; - const result = parseChannelKind(channelName); - expect(result).toBe('internal'); - }); - - it('should return unknown for non-sofia channel', () => { - const channelName = 'voicemail/default/1001'; - const result = parseChannelKind(channelName); - expect(result).toBe('unknown'); - }); - - it('should return unknown for invalid channel format', () => { - const channelName = 'sofia/invalid'; - const result = parseChannelKind(channelName); - expect(result).toBe('unknown'); - }); - - it('should return unknown for undefined input', () => { - const result = parseChannelKind(undefined); - expect(result).toBe('unknown'); - }); - - it('should return unknown for empty input', () => { - const result = parseChannelKind(''); - expect(result).toBe('unknown'); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseChannelUsername.test.ts b/packages/freeswitch/tests/eventParser/parseChannelUsername.test.ts deleted file mode 100644 index f000aaa0f3efb..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseChannelUsername.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { parseChannelUsername, parseContactUsername, parseEventUsername } from '../../src/eventParser/parseChannelUsername'; -import type { EventData } from '../../src/eventParser/parseEventData'; - -function createEventData(data: Record): EventData { - return data as unknown as EventData; -} - -describe('parseChannelUsername', () => { - it('should parse username from originator channel', () => { - const channelName = 'sofia/internal/1001@voip.open.rocket.chat:9999'; - const result = parseChannelUsername(channelName); - expect(result).toBe('1001'); - }); - - it('should parse username from external channel', () => { - const channelName = 'sofia/external/1001@voip.open.rocket.chat:9999'; - const result = parseChannelUsername(channelName); - expect(result).toBe('1001'); - }); - - it('should parse username from originatee channel', () => { - const channelName = 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-spo254ol@open.rocket.chat'; - const result = parseChannelUsername(channelName); - expect(result).toBe('1000'); - }); - - it('should return undefined for non-sofia channel', () => { - const channelName = 'voicemail/default/1001'; - const result = parseChannelUsername(channelName); - expect(result).toBeUndefined(); - }); - - it('should return undefined for invalid channel format', () => { - const channelName = 'sofia/internal/invalid'; - const result = parseChannelUsername(channelName); - expect(result).toBeUndefined(); - }); - - it('should return undefined if username is not number only', () => { - const channelName = 'sofia/internal/10AB@voip-open.rocket.chat:9999'; - const result = parseChannelUsername(channelName); - expect(result).toBeUndefined(); - }); - - it('should return undefined for undefined input', () => { - const result = parseChannelUsername(undefined); - expect(result).toBeUndefined(); - }); -}); - -describe('parseContactUsername', () => { - it('should parse username from contact URI', () => { - const contactUri = '1001-abc123-xyz789@rocket.chat'; - const result = parseContactUsername(contactUri); - expect(result).toBe('1001'); - }); - - it('should return undefined for invalid contact URI', () => { - const contactUri = 'invalid-contact-uri'; - const result = parseContactUsername(contactUri); - expect(result).toBeUndefined(); - }); - - it('should return undefined for non-numeric username', () => { - const contactUri = 'user123-abc123-xyz789@rocket.chat'; - const result = parseContactUsername(contactUri); - expect(result).toBeUndefined(); - }); -}); - -describe('parseEventUsername', () => { - it('should parse username from event data', () => { - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@voip.open.rocket.chat:9999', - }); - - const result = parseEventUsername(eventData); - expect(result).toBe('1001'); - }); - - it('should return undefined when channel name is missing', () => { - const eventData = createEventData({}); - - const result = parseEventUsername(eventData); - expect(result).toBeUndefined(); - }); - - it('should return undefined for non-sofia channel', () => { - const eventData = createEventData({ - 'Channel-Name': 'voicemail/default/1001', - }); - - const result = parseEventUsername(eventData); - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseEventCallId.test.ts b/packages/freeswitch/tests/eventParser/parseEventCallId.test.ts deleted file mode 100644 index cfd45df41db08..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseEventCallId.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { parseEventCallId } from '../../src/eventParser/parseEventCallId'; -import type { EventData } from '../../src/eventParser/parseEventData'; - -describe('parseEventCallId', () => { - const createTestEvent = (overrides: Partial): EventData => ({ - ...overrides, - }); - - it('should use Channel-Call-UUID when IDs match and type is not originator', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'call-123', - 'Other-Type': 'not-originator', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('call-123'); - }); - - it('should use Channel-Call-UUID when IDs differ', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'channel-456', - 'Other-Type': 'originator', - 'Other-Leg-Unique-ID': 'other-789', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('call-123'); - }); - - it('should use Other-Leg-Unique-ID for outbound callers with originator', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'call-123', - 'Other-Type': 'originator', - 'Other-Leg-Unique-ID': 'other-789', - 'Caller-Direction': 'outbound', - 'Other-Leg-Direction': 'inbound', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('other-789'); - }); - - it('should use Channel-Call-UUID when Other-Leg-Unique-ID is missing', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'call-123', - 'Other-Type': 'originator', - 'Caller-Direction': 'outbound', - 'Other-Leg-Direction': 'inbound', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('call-123'); - }); - - it('should use Channel-Call-UUID for inbound callers', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'call-123', - 'Other-Type': 'originator', - 'Other-Leg-Unique-ID': 'other-789', - 'Caller-Direction': 'inbound', - 'Other-Leg-Direction': 'outbound', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('call-123'); - }); - - it('should use Channel-Call-UUID when Other-Leg-Direction is outbound with logical direction', () => { - const event = createTestEvent({ - 'Channel-Call-UUID': 'call-123', - 'Unique-ID': 'call-123', - 'Other-Type': 'originator', - 'Other-Leg-Unique-ID': 'other-789', - 'Caller-Direction': 'outbound', - 'Other-Leg-Direction': 'outbound', - 'Other-Leg-Logical-Direction': 'inbound', - }); - - const result = parseEventCallId(event); - - expect(result).toBe('call-123'); - }); - - it('should return undefined when no call ID is present', () => { - const event = createTestEvent({}); - - const result = parseEventCallId(event); - - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseEventData.test.ts b/packages/freeswitch/tests/eventParser/parseEventData.test.ts deleted file mode 100644 index 46e074439d8a3..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseEventData.test.ts +++ /dev/null @@ -1,5078 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { parseEventData } from '../../src/eventParser/parseEventData'; -import type { EventData } from '../../src/eventParser/parseEventData'; - -function createEventData(data: Record): EventData { - return data as unknown as EventData; -} - -describe('parseEventData', () => { - describe('simple tests', () => { - it('should handle missing required fields', () => { - const eventName = 'CHANNEL_CREATE'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_INIT', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeUndefined(); - }); - - it('should handle invalid sequence number', () => { - const eventName = 'CHANNEL_CREATE'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_INIT', - 'Event-Sequence': 'invalid', - 'Unique-ID': 'test-channel-id', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeUndefined(); - }); - }); - - it('channel with no name, states or timestamp', () => { - const eventName = 'CHANNEL_DESTROY'; - const eventData = createEventData({ - 'Channel-State-Number': '9', - 'Channel-Call-State-Number': '9', - 'Event-Sequence': '1', - 'Unique-ID': 'test-channel-id', - 'Hangup-Cause': 'NORMAL_CLEARING', - 'variable_undefined': undefined, - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.channelName).toBe(''); - expect(result?.channelState).toBe(''); - expect(result?.channelCallState).toBe(''); - expect(result?.hangupCause).toBe('NORMAL_CLEARING'); - expect(result?.firedAt).toBeInstanceOf(Date); - }); - - it('should parse CHANNEL_BRIDGE event', () => { - const eventName = 'CHANNEL_BRIDGE'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'CS_EXECUTE', - 'Channel-State-Number': '4', - 'Channel-Call-State-Number': '4', - 'Event-Sequence': '1', - 'Event-Date-Timestamp': '1749847568969044', - 'Unique-ID': 'test-channel-id', - 'Bridge-A-Unique-ID': 'bridge-a-id', - 'Bridge-B-Unique-ID': 'bridge-b-id', - 'Bridged-To': 'other-channel-id', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'test-channel-id', - eventName: 'CHANNEL_BRIDGE', - sequence: 1, - firedAt: new Date(1749847568969), - callUniqueId: 'test-channel-id', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallStateNumber: '4', - channelCallState: 'CS_EXECUTE', - channelUsername: '1001', - bridgeUniqueIds: ['bridge-a-id', 'bridge-b-id'], - bridgedTo: 'other-channel-id', - metadata: { 'Event-Sequence': '1', 'Event-Date-Timestamp': '1749847568969044' }, - }); - }); - - it('should parse CHANNEL_HANGUP event', () => { - const eventName = 'CHANNEL_HANGUP'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_HANGUP', - 'Channel-Call-State': 'CS_HANGUP', - 'Channel-State-Number': '6', - 'Channel-Call-State-Number': '6', - 'Event-Sequence': '1', - 'Event-Date-Timestamp': '1749847568969044', - 'Unique-ID': 'test-channel-id', - 'Hangup-Cause': 'NORMAL_CLEARING', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'test-channel-id', - eventName: 'CHANNEL_HANGUP', - sequence: 1, - firedAt: new Date(1749847568969), - callUniqueId: 'test-channel-id', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_HANGUP', - channelStateNumber: '6', - channelCallStateNumber: '6', - channelCallState: 'CS_HANGUP', - channelUsername: '1001', - hangupCause: 'NORMAL_CLEARING', - metadata: { 'Event-Sequence': '1', 'Event-Date-Timestamp': '1749847568969044' }, - }); - }); - - it('should parse CHANNEL_ANSWER event', () => { - const eventName = 'CHANNEL_ANSWER'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'CS_EXECUTE', - 'Channel-State-Number': '4', - 'Channel-Call-State-Number': '4', - 'Event-Sequence': '1', - 'Event-Date-Timestamp': '1749847568969044', - 'Unique-ID': 'test-channel-id', - 'Answer-State': 'answered', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'test-channel-id', - eventName: 'CHANNEL_ANSWER', - sequence: 1, - firedAt: new Date(1749847568969), - callUniqueId: 'test-channel-id', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallStateNumber: '4', - channelCallState: 'CS_EXECUTE', - channelUsername: '1001', - answerState: 'answered', - metadata: { 'Event-Sequence': '1', 'Event-Date-Timestamp': '1749847568969044' }, - }); - }); - - it('should parse CHANNEL_PROGRESS_MEDIA event', () => { - const eventName = 'CHANNEL_PROGRESS_MEDIA'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'CS_EXECUTE', - 'Channel-State-Number': '4', - 'Channel-Call-State-Number': '4', - 'Event-Sequence': '1', - 'Event-Date-Timestamp': '1749847568969044', - 'Unique-ID': 'test-channel-id', - 'Channel-Read-Codec-Name': 'PCMU', - 'Channel-Read-Codec-Rate': '8000', - 'Channel-Write-Codec-Name': 'PCMU', - 'Channel-Write-Codec-Rate': '8000', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'test-channel-id', - eventName: 'CHANNEL_PROGRESS_MEDIA', - sequence: 1, - firedAt: new Date(1749847568969), - callUniqueId: 'test-channel-id', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallStateNumber: '4', - channelCallState: 'CS_EXECUTE', - channelUsername: '1001', - metadata: { 'Event-Sequence': '1', 'Event-Date-Timestamp': '1749847568969044' }, - codecs: { read: { name: 'PCMU', rate: '8000' }, write: { nme: 'PCMU', rate: '8000' } }, - }); - }); - - it('should parse CHANNEL_DESTROY event', () => { - const eventName = 'CHANNEL_DESTROY'; - const eventData = createEventData({ - 'Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Channel-State': 'CS_DESTROY', - 'Channel-Call-State': 'CS_DESTROY', - 'Channel-State-Number': '9', - 'Channel-Call-State-Number': '9', - 'Event-Sequence': '1', - 'Event-Date-Timestamp': '1749847568969044', - 'Unique-ID': 'test-channel-id', - 'Hangup-Cause': 'NORMAL_CLEARING', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'test-channel-id', - eventName: 'CHANNEL_DESTROY', - sequence: 1, - firedAt: new Date(1749847568969), - callUniqueId: 'test-channel-id', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_DESTROY', - channelStateNumber: '9', - channelCallStateNumber: '9', - channelCallState: 'CS_DESTROY', - channelUsername: '1001', - hangupCause: 'NORMAL_CLEARING', - metadata: { 'Event-Sequence': '1', 'Event-Date-Timestamp': '1749847568969044' }, - }); - }); -}); - -describe('complete call', () => { - it('should parse CS_NEW CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39834', - 'Channel-State': 'CS_NEW', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '0', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_STATE', - sequence: 39834, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_NEW', - channelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39834', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: undefined, - }); - expect(result?.legs).toBeUndefined(); - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CS_INIT CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39835', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '1', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_STATE', - sequence: 39835, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_INIT', - channelStateNumber: '1', - channelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39835', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_CREATE event', () => { - const eventName = 'CHANNEL_CREATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_CREATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39836', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_ep_codec_string': - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - 'variable_endpoint_disposition': 'DELAYED NEGOTIATION', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_CREATE', - sequence: 39836, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_INIT', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_CREATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39836', - }, - variables: { - direction: 'inbound', - ep_codec_string: - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - endpoint_disposition: 'DELAYED NEGOTIATION', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse RINGING CHANNEL_CALLSTATE event', () => { - const eventName = 'CHANNEL_CALLSTATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_CALLSTATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39837', - 'Original-Channel-Call-State': 'DOWN', - 'Channel-Call-State-Number': '2', - 'Channel-State': 'CS_ROUTING', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_CALLSTATE', - sequence: 39837, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_ROUTING', - channelStateNumber: '2', - channelCallStateNumber: '2', - channelCallState: 'RINGING', - originalChannelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_CALLSTATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39837', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CS_ROUTING CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39838', - 'Channel-State': 'CS_ROUTING', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_STATE', - sequence: 39838, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_ROUTING', - channelStateNumber: '2', - channelCallState: 'RINGING', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39838', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CS_EXECUTE CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39841', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_STATE', - sequence: 39841, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'RINGING', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39841', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_OUTGOING event', () => { - const eventName = 'CHANNEL_OUTGOING'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_OUTGOING', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39852', - 'Channel-State': 'CS_NONE', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '1', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Call-UUID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_OUTGOING', - sequence: 39852, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_NONE', - channelStateNumber: '1', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { profileIndex: '1', profileCreatedTime: new Date(1749847568969), channelCreatedTime: new Date(1749847568969) }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { 'Event-Name': 'CHANNEL_OUTGOING', 'Event-Date-Timestamp': '1749847568969044', 'Event-Sequence': '39852' }, - raw: { 'Channel-Call-UUID': '31450e18-1531-46a0-9c10-4629110e3d23' }, - presenceCallDirection: 'outbound', - caller: '1001', - callee: '1000', - }); - }); - - it('should parse outbound CS_INIT CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39853', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '1', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_STATE', - sequence: 39853, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_INIT', - channelStateNumber: '1', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39853', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CHANNEL_CREATE event', () => { - const eventName = 'CHANNEL_CREATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_CREATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39854', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_sip_invite_domain': '172.99.99.99', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_CREATE', - sequence: 39854, - firedAt: new Date(1749847569008), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_INIT', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_CREATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39854', - }, - variables: { - direction: 'outbound', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - sip_invite_domain: '172.99.99.99', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_ORIGINATE event', () => { - const eventName = 'CHANNEL_ORIGINATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_ORIGINATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39855', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_ORIGINATE', - sequence: 39855, - firedAt: new Date(1749847569008), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_INIT', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_ORIGINATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39855', - }, - variables: { - direction: 'outbound', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CS_ROUTING CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39856', - 'Channel-State': 'CS_ROUTING', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_STATE', - sequence: 39856, - firedAt: new Date(1749847569008), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_ROUTING', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39856', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CS_CONSUME_MEDIA CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39858', - 'Channel-State': 'CS_CONSUME_MEDIA', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '7', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_STATE', - sequence: 39858, - firedAt: new Date(1749847569008), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_CONSUME_MEDIA', - channelStateNumber: '7', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847569008164', - 'Event-Sequence': '39858', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CHANNEL_PROGRESS event', () => { - const eventName = 'CHANNEL_PROGRESS'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_PROGRESS', - 'Event-Date-Timestamp': '1749847569148194', - 'Event-Sequence': '39859', - 'Channel-State': 'CS_CONSUME_MEDIA', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '7', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_sip_invite_domain': '172.99.99.99', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_PROGRESS', - sequence: 39859, - firedAt: new Date(1749847569148), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_CONSUME_MEDIA', - channelStateNumber: '7', - channelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_PROGRESS', - 'Event-Date-Timestamp': '1749847569148194', - 'Event-Sequence': '39859', - }, - variables: { - direction: 'outbound', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - sip_invite_domain: '172.99.99.99', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound RINGING CHANNEL_CALLSTATE event', () => { - const eventName = 'CHANNEL_CALLSTATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_CALLSTATE', - 'Event-Date-Timestamp': '1749847569148194', - 'Event-Sequence': '39860', - 'Original-Channel-Call-State': 'DOWN', - 'Channel-Call-State-Number': '2', - 'Channel-State': 'CS_CONSUME_MEDIA', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '7', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_CALLSTATE', - sequence: 39860, - firedAt: new Date(1749847569148), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_CONSUME_MEDIA', - channelStateNumber: '7', - channelCallStateNumber: '2', - channelCallState: 'RINGING', - originalChannelCallState: 'DOWN', - channelUsername: '1000', - answerState: 'ringing', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_CALLSTATE', - 'Event-Date-Timestamp': '1749847569148194', - 'Event-Sequence': '39860', - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_PROGRESS event', () => { - const eventName = 'CHANNEL_PROGRESS'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_PROGRESS', - 'Event-Date-Timestamp': '1749847569168181', - 'Event-Sequence': '39861', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_extension': '1000', - 'variable_call_timeout': '30', - 'variable_hangup_after_bridge': 'true', - 'variable_current_application_data': 'user/1000@172.99.99.99', - 'variable_current_application': 'bridge', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_originate_disposition': 'failure', - 'variable_DIALSTATUS': 'INVALIDARGS', - 'variable_originated_legs': '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'variable_sip_to_tag': 'rUtyD23mXy9Xj', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_PROGRESS', - sequence: 39861, - firedAt: new Date(1749847569168), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'RINGING', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_PROGRESS', - 'Event-Date-Timestamp': '1749847569168181', - 'Event-Sequence': '39861', - }, - variables: { - direction: 'inbound', - uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_extension: '1000', - call_timeout: '30', - hangup_after_bridge: 'true', - current_application_data: 'user/1000@172.99.99.99', - current_application: 'bridge', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - originate_disposition: 'failure', - DIALSTATUS: 'INVALIDARGS', - originated_legs: '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - sip_to_tag: 'rUtyD23mXy9Xj', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CHANNEL_ANSWER event', () => { - const eventName = 'CHANNEL_ANSWER'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_ANSWER', - 'Event-Date-Timestamp': '1749847572228165', - 'Event-Sequence': '39867', - 'Channel-State': 'CS_CONSUME_MEDIA', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '7', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_is_outbound': 'true', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_ANSWER', - sequence: 39867, - firedAt: new Date(1749847572228), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_CONSUME_MEDIA', - channelStateNumber: '7', - channelCallState: 'RINGING', - channelUsername: '1000', - answerState: 'answered', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_ANSWER', - 'Event-Date-Timestamp': '1749847572228165', - 'Event-Sequence': '39867', - }, - variables: { - direction: 'outbound', - is_outbound: 'true', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_PROGRESS_MEDIA event', () => { - const eventName = 'CHANNEL_PROGRESS_MEDIA'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_PROGRESS_MEDIA', - 'Event-Date-Timestamp': '1749847572249573', - 'Event-Sequence': '39872', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'RINGING', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'early', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '1749847572249573', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_extension': '1000', - 'variable_call_timeout': '30', - 'variable_hangup_after_bridge': 'true', - 'variable_current_application_data': 'user/1000@172.99.99.99', - 'variable_current_application': 'bridge', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_originate_disposition': 'failure', - 'variable_DIALSTATUS': 'INVALIDARGS', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_PROGRESS_MEDIA', - sequence: 39872, - firedAt: new Date(1749847572249), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'RINGING', - channelUsername: '1001', - answerState: 'early', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelProgressMediaTime: new Date(1749847572249), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_PROGRESS_MEDIA', - 'Event-Date-Timestamp': '1749847572249573', - 'Event-Sequence': '39872', - }, - variables: { - direction: 'inbound', - uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_extension: '1000', - call_timeout: '30', - hangup_after_bridge: 'true', - current_application_data: 'user/1000@172.99.99.99', - current_application: 'bridge', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - originate_disposition: 'failure', - DIALSTATUS: 'INVALIDARGS', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CHANNEL_OUTGOING event', () => { - const eventName = 'CHANNEL_OUTGOING'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_OUTGOING', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39881', - 'Channel-State': 'CS_CONSUME_MEDIA', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '7', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_OUTGOING', - sequence: 39881, - firedAt: new Date(1749847572928), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_CONSUME_MEDIA', - channelStateNumber: '7', - channelCallState: 'ACTIVE', - channelUsername: '1000', - answerState: 'answered', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelAnsweredTime: new Date(1749847572228), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_OUTGOING', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39881', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_BRIDGE event', () => { - const eventName = 'CHANNEL_BRIDGE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_BRIDGE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39882', - 'Bridge-A-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Bridge-B-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572928202', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '1749847572249573', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originatee', - 'Other-Leg-Direction': 'outbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Other-Leg-Profile-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Answered-Time': '1749847572228165', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_session_id': '75', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_extension': '1000', - 'variable_call_timeout': '30', - 'variable_hangup_after_bridge': 'true', - 'variable_current_application_data': 'user/1000@172.99.99.99', - 'variable_current_application': 'bridge', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_originated_legs': [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - 'variable_endpoint_disposition': 'ANSWER', - 'variable_originate_causes': ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - 'variable_originate_disposition': 'SUCCESS', - 'variable_DIALSTATUS': 'SUCCESS', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_BRIDGE', - sequence: 39882, - firedAt: new Date(1749847572928), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'ACTIVE', - channelUsername: '1001', - answerState: 'answered', - callDirection: 'inbound', - channelHitDialplan: 'true', - bridgeUniqueIds: ['ebc91302-1b79-4ff3-ac6b-841385e9ea03', '31450e18-1531-46a0-9c10-4629110e3d23'], - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelAnsweredTime: new Date(1749847572928), - channelBridgedTime: new Date(1749847572928), - channelProgressMediaTime: new Date(1749847572249), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Other-Leg', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - type: 'originatee', - direction: 'outbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelAnsweredTime: new Date(1749847572228), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_BRIDGE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39882', - }, - variables: { - direction: 'inbound', - uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - session_id: '75', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_extension: '1000', - call_timeout: '30', - hangup_after_bridge: 'true', - current_application_data: 'user/1000@172.99.99.99', - current_application: 'bridge', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - originated_legs: [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - endpoint_disposition: 'ANSWER', - originate_causes: ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - originate_disposition: 'SUCCESS', - DIALSTATUS: 'SUCCESS', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - it('should parse outbound CS_EXCHANGE_MEDIA CHANNEL_STATE event', () => { - const eventName = 'CHANNEL_STATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39883', - 'Channel-State': 'CS_EXCHANGE_MEDIA', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '5', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_STATE', - sequence: 39883, - firedAt: new Date(1749847572928), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_EXCHANGE_MEDIA', - channelStateNumber: '5', - channelCallState: 'ACTIVE', - channelUsername: '1000', - answerState: 'answered', - callDirection: 'outbound', - channelHitDialplan: 'false', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelAnsweredTime: new Date(1749847572228), - channelBridgedTime: new Date(1749847572928), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_STATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39883', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CALL_UPDATE event', () => { - const eventName = 'CALL_UPDATE'; - const eventData = createEventData({ - 'Event-Name': 'CALL_UPDATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39884', - 'Direction': 'SEND', - 'Sent-Callee-ID-Name': '1001', - 'Sent-Callee-ID-Number': '1001', - 'Bridged-To': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Channel-State': 'CS_EXCHANGE_MEDIA', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '5', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_is_outbound': 'true', - 'variable_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_sip_invite_domain': '172.99.99.99', - 'variable_effective_caller_id_name': 'Extension 1000', - 'variable_effective_caller_id_number': '1000', - 'variable_outbound_caller_id_name': 'FreeSWITCH', - 'variable_outbound_caller_id_number': '0000000000', - 'variable_originate_early_media': 'true', - 'variable_originating_leg_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_originate_endpoint': 'user', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_bridge_to': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_last_bridge_to': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_bridge_channel': 'sofia/internal/1001@99.219.99.169:7443', - 'variable_bridge_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_signal_bond': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_last_sent_callee_id_name': '1001', - 'variable_last_sent_callee_id_number': '1001', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CALL_UPDATE', - sequence: 39884, - firedAt: new Date(1749847572928), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_EXCHANGE_MEDIA', - channelStateNumber: '5', - channelCallState: 'ACTIVE', - channelUsername: '1000', - answerState: 'answered', - callDirection: 'outbound', - channelHitDialplan: 'false', - bridgedTo: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelAnsweredTime: new Date(1749847572228), - channelBridgedTime: new Date(1749847572928), - channelProgressTime: new Date(1749847569148), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CALL_UPDATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39884', - }, - variables: { - direction: 'outbound', - is_outbound: 'true', - uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - sip_invite_domain: '172.99.99.99', - effective_caller_id_name: 'Extension 1000', - effective_caller_id_number: '1000', - outbound_caller_id_name: 'FreeSWITCH', - outbound_caller_id_number: '0000000000', - originate_early_media: 'true', - originating_leg_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - originate_endpoint: 'user', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - bridge_to: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - last_bridge_to: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - bridge_channel: 'sofia/internal/1001@99.219.99.169:7443', - bridge_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - signal_bond: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - last_sent_callee_id_name: '1001', - last_sent_callee_id_number: '1001', - }, - raw: { - 'Direction': 'SEND', - 'Sent-Callee-ID-Name': '1001', - 'Sent-Callee-ID-Number': '1001', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - }); - - it('should parse CALL_UPDATE event', () => { - const eventName = 'CALL_UPDATE'; - const eventData = createEventData({ - 'Event-Name': 'CALL_UPDATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39886', - 'Direction': 'SEND', - 'Sent-Callee-ID-Name': 'Outbound Call', - 'Sent-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Bridged-To': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572928202', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '1749847572249573', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originatee', - 'Other-Leg-Direction': 'outbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Other-Leg-Profile-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Answered-Time': '1749847572228165', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_session_id': '75', - 'variable_sip_from_user': '1001', - 'variable_sip_from_port': '7443', - 'variable_sip_from_uri': '1001@99.219.99.169:7443', - 'variable_sip_from_host': '99.219.99.169', - 'variable_channel_name': 'sofia/internal/1001@99.219.99.169:7443', - 'variable_sip_local_network_addr': '99.219.99.169', - 'variable_sip_network_ip': '177.99.99.183', - 'variable_sip_network_port': '60286', - 'variable_sip_invite_stamp': '1749847568969044', - 'variable_sip_received_ip': '177.99.99.183', - 'variable_sip_received_port': '60286', - 'variable_sip_via_protocol': 'wss', - 'variable_sip_authorized': 'true', - 'variable_sip_acl_authed_by': 'any_v4.auto', - 'variable_sip_from_user_stripped': '1001', - 'variable_sofia_profile_name': 'internal', - 'variable_sofia_profile_url': 'sip:mod_sofia@99.219.99.169:5060', - 'variable_recovery_profile_name': 'internal', - 'variable_sip_allow': 'ACK, CANCEL, INVITE, MESSAGE, BYE, OPTIONS, INFO, NOTIFY, REFER', - 'variable_sip_req_user': '1000', - 'variable_sip_req_port': '7443', - 'variable_sip_req_uri': '1000@99.219.99.169:7443', - 'variable_sip_req_host': '99.219.99.169', - 'variable_sip_to_user': '1000', - 'variable_sip_to_port': '7443', - 'variable_sip_to_uri': '1000@99.219.99.169:7443', - 'variable_sip_to_host': '99.219.99.169', - 'variable_sip_contact_params': 'transport=ws;ob', - 'variable_sip_contact_user': '1001-user1-j7a419rq', - 'variable_sip_contact_uri': '1001-user1-j7a419rq@localhost', - 'variable_sip_contact_host': 'localhost', - 'variable_sip_user_agent': 'SIP.js/0.21.1', - 'variable_sip_via_host': 'localhost', - 'variable_sip_via_rport': '60286', - 'variable_max_forwards': '70', - 'variable_ep_codec_string': - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_extension': '1000', - 'variable_call_timeout': '30', - 'variable_hangup_after_bridge': 'true', - 'variable_current_application_data': 'user/1000@172.99.99.99', - 'variable_current_application': 'bridge', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_originated_legs': [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - 'variable_video_media_flow': 'inactive', - 'variable_text_media_flow': 'inactive', - 'variable_rtp_use_codec_string': 'OPUS,G722,PCMU,PCMA,H264,VP8', - 'variable_remote_video_media_flow': 'inactive', - 'variable_remote_text_media_flow': 'inactive', - 'variable_remote_audio_media_flow': 'sendrecv', - 'variable_audio_media_flow': 'sendrecv', - 'variable_rtp_audio_recv_pt': '109', - 'variable_rtp_use_codec_name': 'opus', - 'variable_rtp_use_codec_fmtp': 'maxplaybackrate=48000;stereo=1;useinbandfec=1', - 'variable_rtp_use_codec_rate': '48000', - 'variable_rtp_use_codec_ptime': '20', - 'variable_rtp_use_codec_channels': '2', - 'variable_rtp_last_audio_codec_string': 'opus@48000h@20i@2c', - 'variable_read_codec': 'opus', - 'variable_original_read_codec': 'opus', - 'variable_read_rate': '48000', - 'variable_original_read_rate': '48000', - 'variable_write_codec': 'opus', - 'variable_write_rate': '48000', - 'variable_remote_audio_ip': '192.168.2.192', - 'variable_remote_audio_port': '54922', - 'variable_remote_audio_rtcp_ip': '192.168.2.192', - 'variable_remote_audio_rtcp_port': '54922', - 'variable_dtmf_type': 'rfc2833', - 'variable_local_media_ip': '172.99.99.99', - 'variable_local_media_port': '16388', - 'variable_advertised_media_ip': '99.219.99.169', - 'variable_rtp_use_timer_name': 'soft', - 'variable_rtp_use_pt': '109', - 'variable_rtp_use_ssrc': '1219061913', - 'variable_rtp_2833_send_payload': '101', - 'variable_rtp_2833_recv_payload': '101', - 'variable_remote_media_ip': '192.168.2.192', - 'variable_remote_media_port': '54922', - 'variable_sip_nat_detected': 'true', - 'variable_sip_to_tag': 'rUtyD23mXy9Xj', - 'variable_sip_from_tag': 'tgtj08fqre', - 'variable_sip_cseq': '1', - 'variable_sip_call_id': '8lapd1dj47tfei96vl6q', - 'variable_sip_full_via': 'SIP/2.0/WSS localhost;branch=z9hG4bK7118990;received=177.99.99.183;rport=60286', - 'variable_sip_full_from': ';tag=tgtj08fqre', - 'variable_sip_full_to': ';tag=rUtyD23mXy9Xj', - 'variable_send_silence_when_idle': '-1', - 'variable_rtp_has_crypto': 'srtp:dtls:AES_CM_128_HMAC_SHA1_80', - 'variable_endpoint_disposition': 'ANSWER', - 'variable_originate_causes': ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - 'variable_originate_disposition': 'SUCCESS', - 'variable_DIALSTATUS': 'SUCCESS', - 'variable_bridge_to': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_last_bridge_to': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_bridge_channel': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'variable_bridge_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_signal_bond': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_last_sent_callee_id_name': '1001', - 'variable_last_sent_callee_id_number': '1001', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CALL_UPDATE', - sequence: 39886, - firedAt: new Date(1749847572928), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'ACTIVE', - channelUsername: '1001', - answerState: 'answered', - callDirection: 'inbound', - channelHitDialplan: 'true', - bridgedTo: '31450e18-1531-46a0-9c10-4629110e3d23', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572928), - channelProgressMediaTime: new Date(1749847572249), - channelBridgedTime: new Date(1749847572928), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Other-Leg', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - type: 'originatee', - direction: 'outbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CALL_UPDATE', - 'Event-Date-Timestamp': '1749847572928202', - 'Event-Sequence': '39886', - }, - variables: { - direction: 'inbound', - uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - session_id: '75', - sip_from_user: '1001', - sip_from_port: '7443', - sip_from_uri: '1001@99.219.99.169:7443', - sip_from_host: '99.219.99.169', - channel_name: 'sofia/internal/1001@99.219.99.169:7443', - sip_local_network_addr: '99.219.99.169', - sip_network_ip: '177.99.99.183', - sip_network_port: '60286', - sip_invite_stamp: '1749847568969044', - sip_received_ip: '177.99.99.183', - sip_received_port: '60286', - sip_via_protocol: 'wss', - sip_authorized: 'true', - sip_acl_authed_by: 'any_v4.auto', - sip_from_user_stripped: '1001', - sofia_profile_name: 'internal', - sofia_profile_url: 'sip:mod_sofia@99.219.99.169:5060', - recovery_profile_name: 'internal', - sip_allow: 'ACK, CANCEL, INVITE, MESSAGE, BYE, OPTIONS, INFO, NOTIFY, REFER', - sip_req_user: '1000', - sip_req_port: '7443', - sip_req_uri: '1000@99.219.99.169:7443', - sip_req_host: '99.219.99.169', - sip_to_user: '1000', - sip_to_port: '7443', - sip_to_uri: '1000@99.219.99.169:7443', - sip_to_host: '99.219.99.169', - sip_contact_params: 'transport=ws;ob', - sip_contact_user: '1001-user1-j7a419rq', - sip_contact_uri: '1001-user1-j7a419rq@localhost', - sip_contact_host: 'localhost', - sip_user_agent: 'SIP.js/0.21.1', - sip_via_host: 'localhost', - sip_via_rport: '60286', - max_forwards: '70', - ep_codec_string: - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_extension: '1000', - call_timeout: '30', - hangup_after_bridge: 'true', - current_application_data: 'user/1000@172.99.99.99', - current_application: 'bridge', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - originated_legs: [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - video_media_flow: 'inactive', - text_media_flow: 'inactive', - rtp_use_codec_string: 'OPUS,G722,PCMU,PCMA,H264,VP8', - remote_video_media_flow: 'inactive', - remote_text_media_flow: 'inactive', - remote_audio_media_flow: 'sendrecv', - audio_media_flow: 'sendrecv', - rtp_audio_recv_pt: '109', - rtp_use_codec_name: 'opus', - rtp_use_codec_fmtp: 'maxplaybackrate=48000;stereo=1;useinbandfec=1', - rtp_use_codec_rate: '48000', - rtp_use_codec_ptime: '20', - rtp_use_codec_channels: '2', - rtp_last_audio_codec_string: 'opus@48000h@20i@2c', - read_codec: 'opus', - original_read_codec: 'opus', - read_rate: '48000', - original_read_rate: '48000', - write_codec: 'opus', - write_rate: '48000', - remote_audio_ip: '192.168.2.192', - remote_audio_port: '54922', - remote_audio_rtcp_ip: '192.168.2.192', - remote_audio_rtcp_port: '54922', - dtmf_type: 'rfc2833', - local_media_ip: '172.99.99.99', - local_media_port: '16388', - advertised_media_ip: '99.219.99.169', - rtp_use_timer_name: 'soft', - rtp_use_pt: '109', - rtp_use_ssrc: '1219061913', - rtp_2833_send_payload: '101', - rtp_2833_recv_payload: '101', - remote_media_ip: '192.168.2.192', - remote_media_port: '54922', - sip_nat_detected: 'true', - sip_to_tag: 'rUtyD23mXy9Xj', - sip_from_tag: 'tgtj08fqre', - sip_cseq: '1', - sip_call_id: '8lapd1dj47tfei96vl6q', - sip_full_via: 'SIP/2.0/WSS localhost;branch=z9hG4bK7118990;received=177.99.99.183;rport=60286', - sip_full_from: ';tag=tgtj08fqre', - sip_full_to: ';tag=rUtyD23mXy9Xj', - send_silence_when_idle: '-1', - rtp_has_crypto: 'srtp:dtls:AES_CM_128_HMAC_SHA1_80', - endpoint_disposition: 'ANSWER', - originate_causes: ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - originate_disposition: 'SUCCESS', - DIALSTATUS: 'SUCCESS', - bridge_to: '31450e18-1531-46a0-9c10-4629110e3d23', - last_bridge_to: '31450e18-1531-46a0-9c10-4629110e3d23', - bridge_channel: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - bridge_uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - signal_bond: '31450e18-1531-46a0-9c10-4629110e3d23', - last_sent_callee_id_name: '1001', - last_sent_callee_id_number: '1001', - }, - raw: { - 'Direction': 'SEND', - 'Sent-Callee-ID-Name': 'Outbound Call', - 'Sent-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - }); - - it('should parse outbound CHANNEL_HANGUP event', () => { - const eventName = 'CHANNEL_HANGUP'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_HANGUP', - 'Event-Date-Timestamp': '1749847575568205', - 'Event-Sequence': '39893', - 'Channel-State': 'CS_EXCHANGE_MEDIA', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '10', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'hangup', - 'Hangup-Cause': 'NORMAL_CLEARING', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_is_outbound': 'true', - 'variable_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_session_id': '76', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_sip_invite_domain': '172.99.99.99', - 'variable_user_context': 'default', - 'variable_effective_caller_id_name': 'Extension 1000', - 'variable_effective_caller_id_number': '1000', - 'variable_outbound_caller_id_name': 'FreeSWITCH', - 'variable_outbound_caller_id_number': '0000000000', - 'variable_callgroup': 'techsupport', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_HANGUP', - sequence: 39893, - firedAt: new Date(1749847575568), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_EXCHANGE_MEDIA', - channelStateNumber: '10', - channelCallState: 'ACTIVE', - channelUsername: '1000', - answerState: 'hangup', - callDirection: 'outbound', - channelHitDialplan: 'false', - hangupCause: 'NORMAL_CLEARING', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - channelBridgedTime: new Date(1749847572928), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_HANGUP', - 'Event-Date-Timestamp': '1749847575568205', - 'Event-Sequence': '39893', - }, - variables: { - direction: 'outbound', - is_outbound: 'true', - uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - session_id: '76', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - sip_invite_domain: '172.99.99.99', - user_context: 'default', - effective_caller_id_name: 'Extension 1000', - effective_caller_id_number: '1000', - outbound_caller_id_name: 'FreeSWITCH', - outbound_caller_id_number: '0000000000', - callgroup: 'techsupport', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse outbound CHANNEL_HANGUP_COMPLETE event', () => { - const eventName = 'CHANNEL_HANGUP_COMPLETE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_HANGUP_COMPLETE', - 'Event-Date-Timestamp': '1749847575568205', - 'Event-Sequence': '39898', - 'Hangup-Cause': 'NORMAL_CLEARING', - 'Channel-State': 'CS_REPORTING', - 'Channel-Call-State': 'HANGUP', - 'Channel-State-Number': '11', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'hangup', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '1749847575568205', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_is_outbound': 'true', - 'variable_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_user_context': 'default', - 'variable_effective_caller_id_name': 'Extension 1000', - 'variable_effective_caller_id_number': '1000', - 'variable_outbound_caller_id_name': 'FreeSWITCH', - 'variable_outbound_caller_id_number': '0000000000', - 'variable_callgroup': 'techsupport', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_HANGUP_COMPLETE', - sequence: 39898, - firedAt: new Date(1749847575568), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_REPORTING', - channelStateNumber: '11', - channelCallState: 'HANGUP', - channelUsername: '1000', - answerState: 'hangup', - callDirection: 'outbound', - channelHitDialplan: 'false', - hangupCause: 'NORMAL_CLEARING', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - channelBridgedTime: new Date(1749847572928), - channelHangupTime: new Date(1749847575568), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_HANGUP_COMPLETE', - 'Event-Date-Timestamp': '1749847575568205', - 'Event-Sequence': '39898', - }, - variables: { - direction: 'outbound', - is_outbound: 'true', - uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - user_context: 'default', - effective_caller_id_name: 'Extension 1000', - effective_caller_id_number: '1000', - outbound_caller_id_name: 'FreeSWITCH', - outbound_caller_id_number: '0000000000', - callgroup: 'techsupport', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - it('should parse CHANNEL_UNBRIDGE event', () => { - const eventName = 'CHANNEL_UNBRIDGE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_UNBRIDGE', - 'Event-Date-Timestamp': '1749847575588235', - 'Event-Sequence': '39899', - 'Bridge-A-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Bridge-B-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Channel-State': 'CS_EXECUTE', - 'Channel-Call-State': 'ACTIVE', - 'Channel-State-Number': '4', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'answered', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572928202', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '1749847572249573', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originatee', - 'Other-Leg-Direction': 'outbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Other-Leg-Profile-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Created-Time': '1749847568969044', - 'Other-Leg-Channel-Answered-Time': '1749847572228165', - 'Other-Leg-Channel-Progress-Time': '1749847569148194', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_call_uuid': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'variable_dialed_extension': '1000', - 'variable_call_timeout': '30', - 'variable_hangup_after_bridge': 'true', - 'variable_current_application_data': 'user/1000@172.99.99.99', - 'variable_current_application': 'bridge', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_originated_legs': [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - 'variable_endpoint_disposition': 'ANSWER', - 'variable_originate_causes': ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - 'variable_originate_disposition': 'SUCCESS', - 'variable_DIALSTATUS': 'SUCCESS', - 'variable_last_bridge_to': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_bridge_channel': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'variable_bridge_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_signal_bond': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_last_sent_callee_id_name': 'Outbound Call', - 'variable_last_sent_callee_id_number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'variable_sip_hangup_phrase': 'OK', - 'variable_last_bridge_hangup_cause': 'NORMAL_CLEARING', - 'variable_last_bridge_proto_specific_hangup_cause': 'sip:200', - 'variable_bridge_hangup_cause': 'NORMAL_CLEARING', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_UNBRIDGE', - sequence: 39899, - firedAt: new Date(1749847575588), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_EXECUTE', - channelStateNumber: '4', - channelCallState: 'ACTIVE', - channelUsername: '1001', - answerState: 'answered', - callDirection: 'inbound', - channelHitDialplan: 'true', - bridgeUniqueIds: ['ebc91302-1b79-4ff3-ac6b-841385e9ea03', '31450e18-1531-46a0-9c10-4629110e3d23'], - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572928), - channelBridgedTime: new Date(1749847572928), - channelProgressMediaTime: new Date(1749847572249), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Other-Leg', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - type: 'originatee', - direction: 'outbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_UNBRIDGE', - 'Event-Date-Timestamp': '1749847575588235', - 'Event-Sequence': '39899', - }, - variables: { - direction: 'inbound', - uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - call_uuid: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - dialed_extension: '1000', - call_timeout: '30', - hangup_after_bridge: 'true', - current_application_data: 'user/1000@172.99.99.99', - current_application: 'bridge', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - originated_legs: [ - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - '31450e18-1531-46a0-9c10-4629110e3d23;Outbound Call;1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - ], - endpoint_disposition: 'ANSWER', - originate_causes: ['31450e18-1531-46a0-9c10-4629110e3d23;NONE', '31450e18-1531-46a0-9c10-4629110e3d23;NONE'], - originate_disposition: 'SUCCESS', - DIALSTATUS: 'SUCCESS', - last_bridge_to: '31450e18-1531-46a0-9c10-4629110e3d23', - bridge_channel: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - bridge_uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - signal_bond: '31450e18-1531-46a0-9c10-4629110e3d23', - last_sent_callee_id_name: 'Outbound Call', - last_sent_callee_id_number: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - sip_hangup_phrase: 'OK', - last_bridge_hangup_cause: 'NORMAL_CLEARING', - last_bridge_proto_specific_hangup_cause: 'sip:200', - bridge_hangup_cause: 'NORMAL_CLEARING', - }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - - expect(result?.raw).toBeUndefined(); - }); - - it('should parse CHANNEL_DESTROY event', () => { - const eventName = 'CHANNEL_DESTROY'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_DESTROY', - 'Event-Date-Timestamp': '1749847575588235', - 'Event-Sequence': '39904', - 'Channel-State': 'CS_REPORTING', - 'Channel-Call-State': 'HANGUP', - 'Channel-State-Number': '12', - 'Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Call-Direction': 'outbound', - 'Presence-Call-Direction': 'outbound', - 'Channel-HIT-Dialplan': 'false', - 'Channel-Presence-ID': '1000@172.99.99.99', - 'Channel-Call-UUID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Answer-State': 'hangup', - 'Hangup-Cause': 'NORMAL_CLEARING', - 'Channel-Read-Codec-Name': 'opus', - 'Channel-Read-Codec-Rate': '48000', - 'Channel-Read-Codec-Bit-Rate': '0', - 'Channel-Write-Codec-Name': 'opus', - 'Channel-Write-Codec-Rate': '48000', - 'Channel-Write-Codec-Bit-Rate': '0', - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Callee-ID-Name': 'Outbound Call', - 'Caller-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Caller-Unique-ID': '31450e18-1531-46a0-9c10-4629110e3d23', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '1749847572228165', - 'Caller-Channel-Progress-Time': '1749847569148194', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '1749847575568205', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '1749847572928202', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'Other-Type': 'originator', - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1001', - 'Other-Leg-Dialplan': 'XML', - 'Other-Leg-Caller-ID-Name': '1001', - 'Other-Leg-Caller-ID-Number': '1001', - 'Other-Leg-Orig-Caller-ID-Name': '1001', - 'Other-Leg-Orig-Caller-ID-Number': '1001', - 'Other-Leg-Callee-ID-Name': 'Outbound Call', - 'Other-Leg-Callee-ID-Number': '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - 'Other-Leg-Network-Addr': '177.99.99.183', - 'Other-Leg-ANI': '1001', - 'Other-Leg-Destination-Number': '1000', - 'Other-Leg-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Other-Leg-Profile-Created-Time': '0', - 'Other-Leg-Channel-Created-Time': '0', - 'Other-Leg-Channel-Answered-Time': '0', - 'Other-Leg-Channel-Progress-Time': '0', - 'Other-Leg-Channel-Progress-Media-Time': '0', - 'Other-Leg-Channel-Hangup-Time': '0', - 'Other-Leg-Channel-Transfer-Time': '0', - 'Other-Leg-Channel-Resurrect-Time': '0', - 'Other-Leg-Channel-Bridged-Time': '0', - 'Other-Leg-Channel-Last-Hold': '0', - 'Other-Leg-Channel-Hold-Accum': '0', - 'Other-Leg-Screen-Bit': 'true', - 'Other-Leg-Privacy-Hide-Name': 'false', - 'Other-Leg-Privacy-Hide-Number': 'false', - 'variable_direction': 'outbound', - 'variable_is_outbound': 'true', - 'variable_uuid': '31450e18-1531-46a0-9c10-4629110e3d23', - 'variable_dialed_user': '1000', - 'variable_dialed_domain': '172.99.99.99', - 'variable_user_context': 'default', - 'variable_effective_caller_id_name': 'Extension 1000', - 'variable_effective_caller_id_number': '1000', - 'variable_outbound_caller_id_name': 'FreeSWITCH', - 'variable_outbound_caller_id_number': '0000000000', - }); - - const result = parseEventData(eventName, eventData); - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - eventName: 'CHANNEL_DESTROY', - sequence: 39904, - firedAt: new Date(1749847575588), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - channelState: 'CS_REPORTING', - channelStateNumber: '12', - channelCallState: 'HANGUP', - channelUsername: '1000', - answerState: 'hangup', - callDirection: 'outbound', - channelHitDialplan: 'false', - hangupCause: 'NORMAL_CLEARING', - legs: { - '31450e18-1531-46a0-9c10-4629110e3d23': { - legName: 'Caller', - uniqueId: '31450e18-1531-46a0-9c10-4629110e3d23', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa@localhost', - profiles: { - '1': { - channelCreatedTime: new Date(1749847568969), - profileCreatedTime: new Date(1749847568969), - channelProgressTime: new Date(1749847569148), - channelAnsweredTime: new Date(1749847572228), - channelBridgedTime: new Date(1749847572928), - channelHangupTime: new Date(1749847575568), - profileIndex: '1', - }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Other-Leg', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - type: 'originator', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - calleeName: 'Outbound Call', - calleeNumber: '1000-LJZ8A9MhHv4Eh6ZQH-h7720gsa', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { - 'Event-Name': 'CHANNEL_DESTROY', - 'Event-Date-Timestamp': '1749847575588235', - 'Event-Sequence': '39904', - }, - variables: { - direction: 'outbound', - is_outbound: 'true', - uuid: '31450e18-1531-46a0-9c10-4629110e3d23', - dialed_user: '1000', - dialed_domain: '172.99.99.99', - user_context: 'default', - effective_caller_id_name: 'Extension 1000', - effective_caller_id_number: '1000', - outbound_caller_id_name: 'FreeSWITCH', - outbound_caller_id_number: '0000000000', - }, - raw: { 'Channel-Call-UUID': '31450e18-1531-46a0-9c10-4629110e3d23' }, - codecs: { - read: { name: 'opus', rate: '48000' }, - write: { nme: 'opus', rate: '48000' }, - }, - presenceCallDirection: 'outbound', - channelPresenceId: '1000@172.99.99.99', - caller: '1001', - callee: '1000', - }); - }); - - it('should parse CHANNEL_CALLSTATE event', () => { - const eventName = 'CHANNEL_CALLSTATE'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_CALLSTATE', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39836', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_ep_codec_string': - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - 'variable_endpoint_disposition': 'DELAYED NEGOTIATION', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_CALLSTATE', - sequence: 39836, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_INIT', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { profileIndex: '1', profileCreatedTime: new Date(1749847568969), channelCreatedTime: new Date(1749847568969) }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { 'Event-Name': 'CHANNEL_CALLSTATE', 'Event-Date-Timestamp': '1749847568969044', 'Event-Sequence': '39836' }, - variables: { - direction: 'inbound', - ep_codec_string: - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - endpoint_disposition: 'DELAYED NEGOTIATION', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - }); - - it('should parse CHANNEL_ANSWER event', () => { - const eventName = 'CHANNEL_ANSWER'; - const eventData = createEventData({ - 'Event-Name': 'CHANNEL_ANSWER', - 'Event-Date-Timestamp': '1749847568969044', - 'Event-Sequence': '39836', - 'Channel-State': 'CS_INIT', - 'Channel-Call-State': 'DOWN', - 'Channel-State-Number': '2', - 'Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Call-Direction': 'inbound', - 'Presence-Call-Direction': 'inbound', - 'Channel-HIT-Dialplan': 'true', - 'Channel-Call-UUID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Answer-State': 'ringing', - 'Caller-Direction': 'inbound', - 'Caller-Logical-Direction': 'inbound', - 'Caller-Username': '1001', - 'Caller-Dialplan': 'XML', - 'Caller-Caller-ID-Name': '1001', - 'Caller-Caller-ID-Number': '1001', - 'Caller-Orig-Caller-ID-Name': '1001', - 'Caller-Orig-Caller-ID-Number': '1001', - 'Caller-Network-Addr': '177.99.99.183', - 'Caller-ANI': '1001', - 'Caller-Destination-Number': '1000', - 'Caller-Unique-ID': 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - 'Caller-Source': 'mod_sofia', - 'Caller-Context': 'default', - 'Caller-Channel-Name': 'sofia/internal/1001@99.219.99.169:7443', - 'Caller-Profile-Index': '1', - 'Caller-Profile-Created-Time': '1749847568969044', - 'Caller-Channel-Created-Time': '1749847568969044', - 'Caller-Channel-Answered-Time': '0', - 'Caller-Channel-Progress-Time': '0', - 'Caller-Channel-Progress-Media-Time': '0', - 'Caller-Channel-Hangup-Time': '0', - 'Caller-Channel-Transfer-Time': '0', - 'Caller-Channel-Resurrect-Time': '0', - 'Caller-Channel-Bridged-Time': '0', - 'Caller-Channel-Last-Hold': '0', - 'Caller-Channel-Hold-Accum': '0', - 'Caller-Screen-Bit': 'true', - 'Caller-Privacy-Hide-Name': 'false', - 'Caller-Privacy-Hide-Number': 'false', - 'variable_direction': 'inbound', - 'variable_ep_codec_string': - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - 'variable_endpoint_disposition': 'DELAYED NEGOTIATION', - }); - - const result = parseEventData(eventName, eventData); - - expect(result).toBeDefined(); - expect(result?.receivedAt).toBeInstanceOf(Date); - - expect(result).toMatchObject({ - channelUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - eventName: 'CHANNEL_ANSWER', - sequence: 39836, - firedAt: new Date(1749847568969), - callUniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - channelState: 'CS_INIT', - channelStateNumber: '2', - channelCallState: 'DOWN', - channelUsername: '1001', - answerState: 'ringing', - callDirection: 'inbound', - channelHitDialplan: 'true', - legs: { - 'ebc91302-1b79-4ff3-ac6b-841385e9ea03': { - legName: 'Caller', - uniqueId: 'ebc91302-1b79-4ff3-ac6b-841385e9ea03', - direction: 'inbound', - logicalDirection: 'inbound', - username: '1001', - callerName: '1001', - callerNumber: '1001', - originalCallerName: '1001', - originalCallerNumber: '1001', - networkAddress: '177.99.99.183', - destinationNumber: '1000', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@99.219.99.169:7443', - profiles: { - '1': { profileIndex: '1', profileCreatedTime: new Date(1749847568969), channelCreatedTime: new Date(1749847568969) }, - }, - dialplan: 'XML', - ani: '1001', - screenBit: 'true', - privacyHideName: 'false', - privacyHideNumber: 'false', - }, - }, - metadata: { 'Event-Name': 'CHANNEL_ANSWER', 'Event-Date-Timestamp': '1749847568969044', 'Event-Sequence': '39836' }, - variables: { - direction: 'inbound', - ep_codec_string: - 'mod_opus.opus@48000h@20i@2c,mod_spandsp.G722@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,CORE_PCM_MODULE.PCMA@8000h@20i@64000b', - endpoint_disposition: 'DELAYED NEGOTIATION', - }, - presenceCallDirection: 'inbound', - caller: '1001', - callee: '1000', - }); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseEventExtensions.test.ts b/packages/freeswitch/tests/eventParser/parseEventExtensions.test.ts deleted file mode 100644 index 35c9c3bacd2c1..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseEventExtensions.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import type { IFreeSwitchChannelEvent } from '@rocket.chat/core-typings'; - -import { parseEventExtensions } from '../../src/eventParser/parseEventExtensions'; - -describe('parseEventExtensions', () => { - const createTestEvent = ( - overrides: Omit, - ): Omit => ({ - ...overrides, - }); - - it('should parse call extensions', () => { - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs: { - 'channel-123': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: 'John Doe', - callerNumber: '1001', - networkAddress: '192.168.1.100', - destinationNumber: '1002', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@192.168.1.100', - raw: {}, - }, - }, - metadata: {}, - raw: {}, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBe('1001'); - expect(result?.callee).toBe('1002'); - }); - - it('should handle contact names', () => { - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs: { - 'channel-123': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: 'John Doe', - callerNumber: '1001', - networkAddress: '192.168.1.100', - destinationNumber: '1002-userid-random@something', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@192.168.1.100', - raw: {}, - }, - }, - metadata: {}, - raw: {}, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBe('1001'); - expect(result?.callee).toBe('1002'); - }); - - it('should handle missing variables', () => { - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs: { - 'channel-123': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1001', - callerName: 'John Doe', - callerNumber: '1001', - networkAddress: '192.168.1.100', - destinationNumber: '1002', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@192.168.1.100', - raw: {}, - }, - }, - metadata: {}, - raw: {}, - variables: {}, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBe('1001'); - expect(result?.callee).toBe('1002'); - }); - - it('should handle calls initiated by outbound channels', () => { - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs: { - 'channel-123': { - legName: 'Caller-Leg', - uniqueId: 'channel-123', - direction: 'outbound', - logicalDirection: 'outbound', - username: '1000', - callerName: 'John Doe', - callerNumber: '1000', - networkAddress: '192.168.1.100', - destinationNumber: '1003', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1001@192.168.1.100', - raw: {}, - }, - 'channel-456': { - legName: 'Other-Leg', - type: 'originatee', - uniqueId: 'channel-456', - direction: 'outbound', - logicalDirection: 'inbound', - username: '1000', - callerName: 'Jane Doe', - callerNumber: '1001', - originalCallerName: 'John Doe', - originalCallerNumber: '1000', - networkAddress: '192.168.1.100', - destinationNumber: '1003-userId-random', - source: 'mod_sofia', - context: 'default', - channelName: 'sofia/internal/1003-userId-random@host', - }, - }, - metadata: {}, - raw: {}, - variables: {}, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBe('1001'); - expect(result?.callee).toBe('1003'); - }); - - it('should handle missing legs', () => { - const legs: any = undefined; - - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs, - metadata: {}, - raw: {}, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBeUndefined(); - expect(result?.callee).toBeUndefined(); - }); - - it('should handle variables', () => { - const event = createTestEvent({ - channelUniqueId: 'channel-123', - eventName: 'CHANNEL_CREATE', - sequence: 1, - firedAt: new Date(), - receivedAt: new Date(), - callUniqueId: 'call-123', - channelName: 'sofia/internal/1001@192.168.1.100', - channelState: 'CS_INIT', - channelCallState: 'DOWN', - channelStateNumber: '1', - channelCallStateNumber: '1', - callDirection: 'outbound', - channelHitDialplan: 'true', - answerState: 'early', - hangupCause: 'NORMAL_CLEARING', - bridgeUniqueIds: ['bridge-1', 'bridge-2'], - bridgedTo: 'bridge-1', - legs: {}, - metadata: {}, - raw: {}, - variables: { - dialed_extension: ['1002'], - }, - }); - - const result = parseEventExtensions(event); - - expect(result).toBeDefined(); - expect(result?.caller).toBeUndefined(); - expect(result?.callee).toBe('1002'); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseEventLeg.test.ts b/packages/freeswitch/tests/eventParser/parseEventLeg.test.ts deleted file mode 100644 index 736decbed755b..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseEventLeg.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import type { EventData } from '../../src/eventParser/parseEventData'; -import { parseEventLeg } from '../../src/eventParser/parseEventLeg'; - -describe('parseEventLeg', () => { - const createTestEvent = (overrides: EventData): EventData => ({ - ...overrides, - }); - - it('should parse caller leg data', () => { - const event = createTestEvent({ - 'Caller-Leg-Direction': 'outbound', - 'Caller-Leg-Logical-Direction': 'outbound', - 'Caller-Leg-Username': '1001', - 'Caller-Leg-Caller-ID-Name': 'John Doe', - 'Caller-Leg-Caller-ID-Number': '1001', - 'Caller-Leg-Network-Addr': '192.168.1.100', - 'Caller-Leg-Destination-Number': '1002', - 'Caller-Leg-Unique-ID': 'channel-123', - 'Caller-Leg-Source': 'mod_sofia', - 'Caller-Leg-Context': 'default', - 'Caller-Leg-Channel-Name': 'sofia/internal/1001@192.168.1.100', - 'Caller-Leg-Profile-Index': '1', - 'Caller-Leg-Profile-Created-Time': '1709123456789000', - 'Caller-Leg-Channel-Created-Time': '1709123457123000', - 'Caller-Leg-Channel-Answered-Time': '1709123458456000', - 'Caller-Leg-Channel-Hangup-Time': '1709123459789000', - }); - - const result = parseEventLeg('Caller-Leg', event); - - expect(result).toBeDefined(); - expect(result?.legName).toBe('Caller-Leg'); - expect(result?.uniqueId).toBe('channel-123'); - expect(result?.direction).toBe('outbound'); - expect(result?.logicalDirection).toBe('outbound'); - expect(result?.username).toBe('1001'); - expect(result?.callerName).toBe('John Doe'); - expect(result?.callerNumber).toBe('1001'); - expect(result?.networkAddress).toBe('192.168.1.100'); - expect(result?.destinationNumber).toBe('1002'); - expect(result?.source).toBe('mod_sofia'); - expect(result?.context).toBe('default'); - expect(result?.channelName).toBe('sofia/internal/1001@192.168.1.100'); - expect(result?.profiles).toBeDefined(); - expect(result?.profiles?.['1']).toBeDefined(); - expect(result?.profiles?.['1'].profileIndex).toBe('1'); - expect(result?.profiles?.['1'].profileCreatedTime?.getTime()).toBe(1709123456789); - expect(result?.profiles?.['1'].channelCreatedTime?.getTime()).toBe(1709123457123); - expect(result?.profiles?.['1'].channelAnsweredTime?.getTime()).toBe(1709123458456); - expect(result?.profiles?.['1'].channelHangupTime?.getTime()).toBe(1709123459789); - expect(result?.raw).toBeUndefined(); - }); - - it('should parse other leg data with type', () => { - const event = createTestEvent({ - 'Other-Leg-Direction': 'inbound', - 'Other-Leg-Logical-Direction': 'inbound', - 'Other-Leg-Username': '1002', - 'Other-Leg-Caller-ID-Name': 'Jane Smith', - 'Other-Leg-Caller-ID-Number': '1002', - 'Other-Leg-Network-Addr': '192.168.1.101', - 'Other-Leg-Destination-Number': '1001', - 'Other-Leg-Unique-ID': 'channel-456', - 'Other-Leg-Source': 'mod_sofia', - 'Other-Leg-Context': 'default', - 'Other-Leg-Channel-Name': 'sofia/internal/1002@192.168.1.101', - 'Other-Type': 'originator', - }); - - const result = parseEventLeg('Other-Leg', event); - - expect(result).toBeDefined(); - expect(result?.legName).toBe('Other-Leg'); - expect(result?.uniqueId).toBe('channel-456'); - expect(result?.direction).toBe('inbound'); - expect(result?.logicalDirection).toBe('inbound'); - expect(result?.username).toBe('1002'); - expect(result?.callerName).toBe('Jane Smith'); - expect(result?.callerNumber).toBe('1002'); - expect(result?.networkAddress).toBe('192.168.1.101'); - expect(result?.destinationNumber).toBe('1001'); - expect(result?.source).toBe('mod_sofia'); - expect(result?.context).toBe('default'); - expect(result?.channelName).toBe('sofia/internal/1002@192.168.1.101'); - expect(result?.type).toBe('originator'); - expect(result?.raw).toBeUndefined(); - }); - - it('should return undefined when unique ID is missing', () => { - const event = createTestEvent({ - 'Caller-Leg-Direction': 'outbound', - 'Caller-Leg-Logical-Direction': 'outbound', - 'Caller-Leg-Username': '1001', - 'Caller-Leg-Caller-ID-Name': 'John Doe', - 'Caller-Leg-Caller-ID-Number': '1001', - 'Caller-Leg-Network-Addr': '192.168.1.100', - 'Caller-Leg-Destination-Number': '1002', - 'Caller-Leg-Source': 'mod_sofia', - 'Caller-Leg-Context': 'default', - 'Caller-Leg-Channel-Name': 'sofia/internal/1001@192.168.1.100', - }); - - const result = parseEventLeg('Caller-Leg', event); - - expect(result).toBeUndefined(); - }); - - it('should handle missing optional fields', () => { - const event = createTestEvent({ - 'Caller-Leg-Direction': 'outbound', - 'Caller-Leg-Logical-Direction': 'outbound', - 'Caller-Leg-Username': '1001', - 'Caller-Leg-Unique-ID': 'channel-123', - }); - - const result = parseEventLeg('Caller-Leg', event); - - expect(result).toBeDefined(); - expect(result?.legName).toBe('Caller-Leg'); - expect(result?.uniqueId).toBe('channel-123'); - expect(result?.direction).toBe('outbound'); - expect(result?.logicalDirection).toBe('outbound'); - expect(result?.username).toBe('1001'); - expect(result?.callerName).toBeUndefined(); - expect(result?.callerNumber).toBeUndefined(); - expect(result?.networkAddress).toBeUndefined(); - expect(result?.destinationNumber).toBeUndefined(); - expect(result?.source).toBeUndefined(); - expect(result?.context).toBeUndefined(); - expect(result?.channelName).toBeUndefined(); - expect(result?.profiles).toBeUndefined(); - expect(result?.raw).toBeUndefined(); - }); - - it('should filter out empty values from raw data', () => { - const event = createTestEvent({ - 'Caller-Direction': 'outbound', - 'Caller-Logical-Direction': 'outbound', - 'Caller-Username': '1001', - 'Caller-Unique-ID': 'channel-123', - 'Caller-Empty-Value': '', - 'Caller-Undefined-Value': undefined, - 'Caller-Unused-Variable': 'unused-value', - }); - - const result = parseEventLeg('Caller', event); - - expect(result).toBeDefined(); - expect(result?.raw).toBeDefined(); - expect(result?.raw['Empty-Value']).toBeUndefined(); - expect(result?.raw['Undefined-Value']).toBeUndefined(); - }); -}); diff --git a/packages/freeswitch/tests/eventParser/parseTimestamp.test.ts b/packages/freeswitch/tests/eventParser/parseTimestamp.test.ts deleted file mode 100644 index 86cf941a1b597..0000000000000 --- a/packages/freeswitch/tests/eventParser/parseTimestamp.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; - -import { parseTimestamp } from '../../src/eventParser/parseTimestamp'; - -describe('parseTimestamp', () => { - it('should parse a valid timestamp', () => { - const timestamp = '1709123456789123'; - const result = parseTimestamp(timestamp); - - expect(result).toBeInstanceOf(Date); - expect(result?.getTime()).toBe(1709123456789); - }); - - it('should return undefined for undefined input', () => { - const result = parseTimestamp(undefined); - - expect(result).toBeUndefined(); - }); - - it('should return undefined for zero timestamp', () => { - const result = parseTimestamp('0'); - - expect(result).toBeUndefined(); - }); - - it('should return undefined for invalid timestamp', () => { - const result = parseTimestamp('invalid'); - - expect(result).toBeUndefined(); - }); - - it('should return undefined for negative timestamp', () => { - const result = parseTimestamp('-1709123456789'); - - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/freeswitch/tests/mapUserData.test.ts b/packages/freeswitch/tests/mapUserData.test.ts deleted file mode 100644 index 44b55db492c21..0000000000000 --- a/packages/freeswitch/tests/mapUserData.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { mapUserData } from '../src/utils/mapUserData'; - -expect(() => mapUserData(undefined as unknown as any)).toThrow(); -expect(() => mapUserData({ extension: '15' })).toThrow('Invalid user identification.'); - -test.each([ - [{ userid: '1' }, { extension: '1', groups: [], status: 'UNKNOWN' }], - [ - { userid: '1', context: 'default' }, - { extension: '1', context: 'default', groups: [], status: 'UNKNOWN' }, - ], - [ - { - userid: '1', - context: 'default', - domain: 'domainName', - groups: 'default|employee', - contact: 'no', - callgroup: 'call group', - effective_caller_id_name: 'caller_id_name', - effective_caller_id_number: 'caller_id_number', - }, - { - extension: '1', - context: 'default', - domain: 'domainName', - groups: ['default', 'employee'], - contact: 'no', - callGroup: 'call group', - callerName: 'caller_id_name', - callerNumber: 'caller_id_number', - status: 'UNKNOWN', - }, - ], -])('parse user status: %p', (input, output) => { - expect(mapUserData(input)).toMatchObject(output); -}); diff --git a/packages/freeswitch/tests/parseUserList.test.ts b/packages/freeswitch/tests/parseUserList.test.ts deleted file mode 100644 index 0d4aa7bd0347f..0000000000000 --- a/packages/freeswitch/tests/parseUserList.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { makeFreeSwitchResponse } from './utils/makeFreeSwitchResponse'; -import { parseUserList } from '../src/utils/parseUserList'; - -test.each(['', undefined, 200 as unknown as any, '\nsomething'])('Invalid FreeSwitch responses', (input) => { - expect(() => parseUserList({ _body: input })).toThrow('Invalid response from FreeSwitch server.'); -}); - -test('Should return an empty list when there is no userid column', () => { - expect( - parseUserList( - makeFreeSwitchResponse([ - ['aaa', 'bbb', 'ccc'], - ['AData', 'BData', 'CData'], - ['AData2', 'BData2', 'CData2'], - ['AData3', 'BData3', 'CData3'], - ]), - ), - ).toMatchObject([]); -}); - -test('Should return an empty list when there is no lowercase userid column', () => { - expect( - parseUserList( - makeFreeSwitchResponse([ - ['aaa', 'bbb', 'ccc', 'USERID'], - [], - ['AData', 'BData', 'CData', '15'], - ['AData2', 'BData2', 'CData2', '20'], - ['AData3', 'BData3', 'CData3', '30'], - ]), - ), - ).toMatchObject([]); -}); - -test(`Should return an empty list when all records' userid is either missing or equal to +OK`, () => { - expect( - parseUserList( - makeFreeSwitchResponse([ - ['aaa', 'bbb', 'ccc', 'userid'], - ['AData', 'BData', 'CData'], - ['AData2', 'BData2', 'CData2', ''], - ['AData3', 'BData3', 'CData3', '+OK'], - ]), - ), - ).toMatchObject([]); -}); - -test.each([ - [ - [['userid'], ['1']], - [ - { - userid: '1', - }, - ], - ], - [ - [['userid'], ['1'], ['2'], ['3']], - [ - { - userid: '1', - }, - { - userid: '2', - }, - { - userid: '3', - }, - ], - ], - [ - [ - ['userid', 'group', 'name'], - ['1', 'default', 'User 1'], - ['2', 'default', 'User 2'], - ['3', 'default', 'User 3'], - ], - [ - { - userid: '1', - groups: 'default', - name: 'User 1', - }, - { - userid: '2', - groups: 'default', - name: 'User 2', - }, - { - userid: '3', - groups: 'default', - name: 'User 3', - }, - ], - ], - [ - [ - ['userid', 'name'], - ['1', 'User 1', 'AnotherValue'], - ['2', 'User 2'], - ['3', 'User 3'], - ], - [ - { - userid: '1', - name: 'User 1', - column2: 'AnotherValue', - }, - { - userid: '2', - name: 'User 2', - }, - { - userid: '3', - name: 'User 3', - }, - ], - ], -])('parse valid user list: %p', (input, output) => { - expect(parseUserList(makeFreeSwitchResponse(input))).toMatchObject(output); -}); - -test.each([ - [ - [['userid'], ['1'], ['1']], - [ - { - userid: '1', - }, - ], - ], - [ - [['userid'], ['1'], ['2'], ['1'], ['2'], ['3'], ['3']], - [ - { - userid: '1', - }, - { - userid: '2', - }, - { - userid: '3', - }, - ], - ], - [ - [ - ['userid', 'group'], - ['1', 'default'], - ['1', 'employee'], - ], - [ - { - userid: '1', - groups: 'default|employee', - }, - ], - ], - [ - // When there's multiple records for the same user, join the group names from all of them into the data from the first record - [ - ['userid', 'group'], - ['1', 'default'], - ['2', 'default'], - ['1', 'employee'], - ['2', 'manager'], - ['3', 'default'], - ['3', 'owner'], - ], - [ - { - userid: '1', - groups: 'default|employee', - }, - { - userid: '2', - groups: 'default|manager', - }, - { - userid: '3', - groups: 'default|owner', - }, - ], - ], - [ - // When there's multiple records for the same user without group names, use only the data from the first of them - [ - ['userid', 'something_else'], - ['1', '1.1'], - ['1', '1.2'], - ['2', '2.1'], - ['2', '2.2', 'extra_value'], - ['3', ''], - ['3', '3.2'], - ['3', '3.3'], - ], - [ - { - userid: '1', - something_else: '1.1', - }, - { - userid: '2', - something_else: '2.1', - }, - { - userid: '3', - something_else: '', - }, - ], - ], -])('parse user list with duplicate userids: %p', (input, output) => { - expect(parseUserList(makeFreeSwitchResponse(input))).toMatchObject(output); -}); diff --git a/packages/freeswitch/tests/parseUserStatus.test.ts b/packages/freeswitch/tests/parseUserStatus.test.ts deleted file mode 100644 index 73e2f0599097c..0000000000000 --- a/packages/freeswitch/tests/parseUserStatus.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { parseUserStatus } from '../src/utils/parseUserStatus'; - -test.each([ - ['', 'UNKNOWN'], - [undefined, 'UNKNOWN'], - ['error/user_not_registered', 'UNREGISTERED'], - ['ERROR/user_not_registered', 'UNKNOWN'], - ['sofia/user_data', 'REGISTERED'], - ['sofia/', 'REGISTERED'], - ['SOFIA/', 'UNKNOWN'], - ['luana/', 'UNKNOWN'], -])('parse user status: %p', (input, output) => { - expect(parseUserStatus(input)).toBe(output); -}); diff --git a/packages/freeswitch/tests/utils/makeFreeSwitchResponse.ts b/packages/freeswitch/tests/utils/makeFreeSwitchResponse.ts deleted file mode 100644 index 58c2da59a1fec..0000000000000 --- a/packages/freeswitch/tests/utils/makeFreeSwitchResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { StringMap } from 'esl'; - -export const makeFreeSwitchResponse = (lines: string[][]): StringMap => ({ - _body: lines.map((columns) => columns.join('|')).join('\n'), -}); diff --git a/packages/freeswitch/tsconfig.json b/packages/freeswitch/tsconfig.json deleted file mode 100644 index c56c8ee101524..0000000000000 --- a/packages/freeswitch/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@rocket.chat/tsconfig/server.json", - "compilerOptions": { - "declaration": true, - "rootDir": "./src", - "outDir": "./dist" - }, - "include": ["./src/**/*"], -} diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index ea619460455ef..d9c3f1c3ecde7 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2328,7 +2328,6 @@ "Forward_to_user": "Forward to user", "Forwarding": "Forwarding", "Free": "Free", - "FreeSwitch_setup_required": "FreeSwitch setup required", "Free_Apps": "Free Apps", "Free_Edition": "Free edition", "Frequently_Used": "Frequently Used", @@ -5635,18 +5634,12 @@ "VoIP": "VoIP", "VoIP_TeamCollab": "Team voice calls (VoIP)", "VoIP_TeamCollab_Beta_Alert": "This feature is currently in Beta, please report any issues to Rocket.Chat support", - "VoIP_TeamCollab_Description": "Set up FreeSwitch for VoIP in Team collaboration", + "VoIP_TeamCollab_Description": "Set up VoIP in Team collaboration", "VoIP_TeamCollab_Enabled": "Enabled", "VoIP_TeamCollab_Feature1": "<0>Direct calling: Instantly start or receive calls with team members within your Rocket.Chat workspace.", "VoIP_TeamCollab_Feature2": "<0>Extension management: Admins can assign unique extensions to users, enabling quick, direct dialing both from inside and outside your organization.", "VoIP_TeamCollab_Feature3": "<0>Call transfers: Seamlessly transfer active calls to ensure users reach the right team member.", "VoIP_TeamCollab_Feature4": "<0>Availability settings: Users can control their availability to receive calls, enhancing flexibility.", - "VoIP_TeamCollab_FreeSwitch": "FreeSwitch Integration", - "VoIP_TeamCollab_FreeSwitch_Host": "FreeSwitch Host", - "VoIP_TeamCollab_FreeSwitch_Password": "FreeSwitch Password", - "VoIP_TeamCollab_FreeSwitch_Port": "FreeSwitch Port", - "VoIP_TeamCollab_FreeSwitch_Timeout": "FreeSwitch Request Timeout", - "VoIP_TeamCollab_FreeSwitch_WebSocket_Path": "WebSocket Path", "VoIP_TeamCollab_Ice_Gathering_Timeout": "ICE Gathering Timeout", "VoIP_TeamCollab_Ice_Gathering_Timeout_Description": "Time to wait for ICE Gathering to complete before sending. Low values may prevent Ice Servers from being used, while high values may delay the start of VoIP calls if an invalid Ice Server is specified.", "VoIP_TeamCollab_Ice_Servers": "ICE Servers", @@ -5660,7 +5653,6 @@ "VoIP_TeamCollab_Drachtio_Password": "Drachtio Password", "VoIP_TeamCollab_SIP_Server_Host": "SIP Server Host", "VoIP_TeamCollab_SIP_Server_Port": "SIP Server Port", - "VoIP_available_setup_freeswitch_server_details": "VoIP is available but the FreeSwitch server details need to be set up from the team voice call settings.", "VoIP_device_permission_required": "Mic/speaker access required", "VoIP_device_permission_required_description": "Your web browser stopped {{workspaceUrl}} from using your microphone and/or speaker.\n\nAllow speaker and microphone access in your browser settings to prevent seeing this message again.", "VoIP_allow_and_call": "Allow and call", diff --git a/packages/i18n/src/locales/nb.i18n.json b/packages/i18n/src/locales/nb.i18n.json index c107825ea4be1..eaf6a0b084c55 100644 --- a/packages/i18n/src/locales/nb.i18n.json +++ b/packages/i18n/src/locales/nb.i18n.json @@ -2304,7 +2304,6 @@ "Forward_to_user": "Videresend til bruker", "Forwarding": "Videresending", "Free": "Gratis", - "FreeSwitch_setup_required": "FreeSwitch-oppsett kreves", "Free_Apps": "Gratis-apper", "Free_Edition": "Gratisversjon", "Frequently_Used": "Ofte brukt", @@ -5607,25 +5606,18 @@ "VoIP": "VoIP", "VoIP_TeamCollab": "Teamtaleanrop (VoIP)", "VoIP_TeamCollab_Beta_Alert": "Denne funksjonen er for øyeblikket i betaversjon, vennligst rapporter eventuelle problemer til Rocket.Chat-support", - "VoIP_TeamCollab_Description": "Sett opp FreeSwitch for VoIP i teamsamarbeid", + "VoIP_TeamCollab_Description": "Sett opp VoIP i teamsamarbeid", "VoIP_TeamCollab_Enabled": "Aktivert", "VoIP_TeamCollab_Feature1": "<0>Direkteanrop: Start eller motta samtaler med teammedlemmer umiddelbart i Rocket.Chat-arbeidsområdet.", "VoIP_TeamCollab_Feature2": "<0>Administrasjon av utvidelser: Administratorer kan tilordne unike utvidelser til brukere, noe som muliggjør rask, direkte oppringing både innenfra og utenfor organisasjonen.", "VoIP_TeamCollab_Feature3": "<0>Anropsoverføringer: Overfør sømløst aktive anrop for å sikre at brukerne når rett teammedlem.", "VoIP_TeamCollab_Feature4": "<0>Tilgjengelighetsinnstillinger: Brukere kan kontrollere tilgjengeligheten for å motta anrop, noe som øker fleksibiliteten.", - "VoIP_TeamCollab_FreeSwitch": "FreeSwitch-integrering", - "VoIP_TeamCollab_FreeSwitch_Host": "FreeSwitch-vert", - "VoIP_TeamCollab_FreeSwitch_Password": "FreeSwitch-passord", - "VoIP_TeamCollab_FreeSwitch_Port": "FreeSwitch-port", - "VoIP_TeamCollab_FreeSwitch_Timeout": "Timeout for FreeSwitch-forespørsel", - "VoIP_TeamCollab_FreeSwitch_WebSocket_Path": "WebSocket-bane", "VoIP_TeamCollab_Ice_Gathering_Timeout": "Tidsavbrudd for ICE-samling", "VoIP_TeamCollab_Ice_Gathering_Timeout_Description": "Tid det tar å vente på at ICE Gathering skal fullføres før sending. Lave verdier kan forhindre at Ice-servere brukes, mens høye verdier kan forsinke starten av VoIP-anrop hvis en ugyldig Ice-server er angitt.", "VoIP_TeamCollab_Ice_Servers": "ICE-servere", "VoIP_TeamCollab_Ice_Servers_Description": "En liste over isservere (STUN og/eller TURN), atskilt med komma. \n Brukernavn, passord og port er tillatt i formatet `brukernavn:passord@stun:host:port` eller `brukernavn:passord@turn:host:port`. \n Både brukernavn og passord kan være html-kodet.", "VoIP_TeamCollab_WebRTC": "WebRTC-innstillinger", "VoIP_TeamCollab_SIP_Integration": "SIP-integrering", - "VoIP_available_setup_freeswitch_server_details": "VoIP er tilgjengelig, men FreeSwitch-serverdetaljene må konfigureres fra innstillingene for teamtaleanrop.", "VoIP_device_permission_required": "Tilgang til mikrofon/høyttaler kreves", "VoIP_device_permission_required_description": "Nettleseren din stoppet {{workspaceUrl}} fra å bruke mikrofonen og/eller høyttaleren.\n\nTillat høyttaler- og mikrofontilgang i nettleserinnstillingene for å unngå å se denne meldingen igjen.", "VoIP_allow_and_call": "Tillat og ring", diff --git a/packages/i18n/src/locales/nn.i18n.json b/packages/i18n/src/locales/nn.i18n.json index 0b13115e0ca8b..9309f54ade4f2 100644 --- a/packages/i18n/src/locales/nn.i18n.json +++ b/packages/i18n/src/locales/nn.i18n.json @@ -2215,7 +2215,6 @@ "Forward_to_user": "Videresend til bruker", "Forwarding": "Videresending", "Free": "Gratis", - "FreeSwitch_setup_required": "FreeSwitch-oppsett kreves", "Free_Apps": "Gratis-apper", "Free_Edition": "Gratisversjon", "Frequently_Used": "Ofte brukt", @@ -5113,18 +5112,12 @@ "VoIP": "VoIP", "VoIP_TeamCollab": "Teamtaleanrop (VoIP)", "VoIP_TeamCollab_Beta_Alert": "Denne funksjonen er for øyeblikket i betaversjon, vennligst rapporter eventuelle problemer til Rocket.Chat-support", - "VoIP_TeamCollab_Description": "Sett opp FreeSwitch for VoIP i teamsamarbeid", + "VoIP_TeamCollab_Description": "Sett opp VoIP i teamsamarbeid", "VoIP_TeamCollab_Enabled": "Aktivert", "VoIP_TeamCollab_Feature1": "<0>Direkteanrop: Start eller motta samtaler med teammedlemmer umiddelbart i Rocket.Chat-arbeidsområdet.", "VoIP_TeamCollab_Feature2": "<0>Administrasjon av utvidelser: Administratorer kan tilordne unike utvidelser til brukere, noe som muliggjør rask, direkte oppringing både innenfra og utenfor organisasjonen.", "VoIP_TeamCollab_Feature3": "<0>Anropsoverføringer: Overfør sømløst aktive anrop for å sikre at brukerne når rett teammedlem.", "VoIP_TeamCollab_Feature4": "<0>Tilgjengelighetsinnstillinger: Brukere kan kontrollere tilgjengeligheten for å motta anrop, noe som øker fleksibiliteten.", - "VoIP_TeamCollab_FreeSwitch_Host": "FreeSwitch-vert", - "VoIP_TeamCollab_FreeSwitch_Password": "FreeSwitch-passord", - "VoIP_TeamCollab_FreeSwitch_Port": "FreeSwitch-port", - "VoIP_TeamCollab_FreeSwitch_Timeout": "Timeout for FreeSwitch-forespørsel", - "VoIP_TeamCollab_FreeSwitch_WebSocket_Path": "WebSocket-bane", - "VoIP_available_setup_freeswitch_server_details": "VoIP er tilgjengelig, men FreeSwitch-serverdetaljene må konfigureres fra innstillingene for teamtaleanrop.", "Voice_Call_Extension": "Utvidelse for taleanrop", "Voice_call": "Taleanrop", "Voice_call_extension": "Utvidelse for taleanrop", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 009a1abf6933b..d86916774961c 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -2251,7 +2251,6 @@ "Forward_to_user": "Encaminhar ao usuário", "Forwarding": "Encaminhando", "Free": "Grátis", - "FreeSwitch_setup_required": "É necessária a configuração do FreeSwitch", "Free_Apps": "Aplicativos gratuitos", "Free_Edition": "Edição gratuita", "Frequently_Used": "Usados frequentemente", @@ -5484,22 +5483,16 @@ "VoIP": "VoIP", "VoIP_TeamCollab": "Chamadas de voz para equipe (VoIP)", "VoIP_TeamCollab_Beta_Alert": "Este recurso está atualmente em Beta, por favor, reporte quaisquer problemas ao suporte da Rocket.Chat.", - "VoIP_TeamCollab_Description": "Configurar o FreeSwitch para VoIP na colaboração em equipe", + "VoIP_TeamCollab_Description": "Configurar o VoIP na colaboração em equipe", "VoIP_TeamCollab_Enabled": "Habilitado", "VoIP_TeamCollab_Feature1": "<0>Chamadas diretas: Inicie ou receba chamadas instantaneamente com membros da equipe dentro do seu workspace do Rocket.Chat.", "VoIP_TeamCollab_Feature2": "<0>Gerenciamento de extensões: Os administradores podem atribuir extensões exclusivas aos usuários, permitindo a discagem rápida e direta de dentro e de fora da sua organização.", "VoIP_TeamCollab_Feature3": "<0>Transferência de chamadas: Transfira perfeitamente as chamadas ativas para garantir que os usuários cheguem ao membro certo da equipe.", "VoIP_TeamCollab_Feature4": "<0>Configurações de disponibilidade: Os usuários podem controlar sua disponibilidade para receber chamadas, aumentando a flexibilidade.", - "VoIP_TeamCollab_FreeSwitch_Host": "Host do FreeSwitch", - "VoIP_TeamCollab_FreeSwitch_Password": "Senha do FreeSwitch", - "VoIP_TeamCollab_FreeSwitch_Port": "Porta do FreeSwitch", - "VoIP_TeamCollab_FreeSwitch_Timeout": "Tempo limite de solicitação do FreeSwitch", - "VoIP_TeamCollab_FreeSwitch_WebSocket_Path": "URL do WebSocket", "VoIP_TeamCollab_Ice_Gathering_Timeout": "Tempo limite de coleta de ICE", "VoIP_TeamCollab_Ice_Gathering_Timeout_Description": "Tempo de espera para que a coleta ICE seja concluído antes do envio. Valores baixos podem impedir que os Ice Servers sejam usados, enquanto valores altos podem atrasar o início das chamadas VoIP se um servidor ICE inválido for especificado.", "VoIP_TeamCollab_Ice_Servers": "Servidores ICE", "VoIP_TeamCollab_Ice_Servers_Description": "Uma lista de Ice Servers (STUN e/ou TURN), separados por vírgula. \n O nome de usuário, a senha e a porta são permitidos no formato `nome de usuário:senha@stun:host:porta` ou `nome de usuário:senha@turn:host:porta`. \n Tanto o nome de usuário quanto a senha podem ser codificados em html.", - "VoIP_available_setup_freeswitch_server_details": "O VoIP está disponível, mas os detalhes do servidor FreeSwitch precisam ser definidos nas configurações de chamada de voz da equipe.", "Voice_Call": "Chamada de voz", "Voice_Call_Extension": "Extensão de chamada de voz", "Voice_and_omnichannel": "Voz e omnichannel", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index 7bedea297d6ed..7e92717cad2e1 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -2262,7 +2262,6 @@ "Forward_to_user": "Vidarebefodra till användare", "Forwarding": "Vidarebefordran", "Free": "Kostnadsfritt", - "FreeSwitch_setup_required": "Installation av FreeSwitch krävs", "Free_Apps": "Kostnadsfria appar", "Free_Edition": "Kostnadsfri utgåva", "Frequently_Used": "Ofta använd", @@ -5520,22 +5519,16 @@ "VoIP": "VoIP", "VoIP_TeamCollab": "Röstsamtal i grupp (VoIP)", "VoIP_TeamCollab_Beta_Alert": "Denna funktion är för närvarande i Beta, vänligen rapportera eventuella problem till Rocket.Chat support", - "VoIP_TeamCollab_Description": "Konfigurera FreeSwitch för VoIP i Teamsamarbete", + "VoIP_TeamCollab_Description": "Konfigurera VoIP i Teamsamarbete", "VoIP_TeamCollab_Enabled": "Aktiverad", "VoIP_TeamCollab_Feature1": "<0>Direktsamtal: Starta eller ta emot samtal direkt med teammedlemmar inom din Rocket.chat-arbetsyta.", "VoIP_TeamCollab_Feature2": "<0>Hantering av anknytningar: Administratörer kan tilldela unika anknytningar till användare, vilket möjliggör snabb direktuppringning både inom och utanför organisationen.", "VoIP_TeamCollab_Feature3": "<0>Samtalsöverföringar: Överför sömlöst aktiva samtal för att säkerställa att användarna når rätt teammedlem.", "VoIP_TeamCollab_Feature4": "<0>Tillgänglighetsinställningar: Användare kan styra sin tillgänglighet för att ta emot samtal, vilket ökar flexibiliteten.", - "VoIP_TeamCollab_FreeSwitch_Host": "FreeSwitch Värd", - "VoIP_TeamCollab_FreeSwitch_Password": "FreeSwitch Lösenord", - "VoIP_TeamCollab_FreeSwitch_Port": "FreeSwitch-port", - "VoIP_TeamCollab_FreeSwitch_Timeout": "Timeout för FreeSwitch-begäran", - "VoIP_TeamCollab_FreeSwitch_WebSocket_Path": "WebSocket sökväg", "VoIP_TeamCollab_Ice_Gathering_Timeout": "Tidsgräns för ICE-insamling", "VoIP_TeamCollab_Ice_Gathering_Timeout_Description": "Tid att vänta på att ICE Gathering ska slutföras innan sändning. Låga värden kan förhindra att ICE servrar används, medan höga värden kan försena starten av VoIP-samtal om en ogiltig ICE server anges.", "VoIP_TeamCollab_Ice_Servers": "ICE-servrar", "VoIP_TeamCollab_Ice_Servers_Description": "En lista över ICE servrar (STUN och/eller TURN), åtskilda med kommatecken. \n Användarnamn, lösenord och port tillåts i formatet `användarnamn:lösenord@stun:host:port` eller `användarnamn:lösenord@turn:host:port`. \n Både användarnamn och lösenord kan vara html-kodade.", - "VoIP_available_setup_freeswitch_server_details": "VoIP är tillgängligt men FreeSwitch-serveruppgifterna måste ställas in från inställningarna för röstsamtal i teamet.", "VoIP_device_permission_required": "Tillgång till mikrofon/högtalare krävs", "VoIP_device_permission_required_description": "Din webbläsare hindrade {{workspaceUrl}} från att använda mikrofonen och/eller högtalaren.\n\nTillåt högtalar- och mikrofonåtkomst i webbläsarens inställningar för att förhindra att detta meddelande visas igen.", "VoIP_allow_and_call": "Tillåt och ring", diff --git a/packages/model-typings/src/index.ts b/packages/model-typings/src/index.ts index 15f1405e7840f..64aed39df3c38 100644 --- a/packages/model-typings/src/index.ts +++ b/packages/model-typings/src/index.ts @@ -14,9 +14,6 @@ export * from './models/IEmojiCustomModel'; export * from './models/IExportOperationsModel'; export * from './models/IFederationKeysModel'; export * from './models/IFederationServersModel'; -export * from './models/IFreeSwitchChannelModel'; -export * from './models/IFreeSwitchChannelEventModel'; -export * from './models/IFreeSwitchChannelEventDeltaModel'; export * from './models/IInstanceStatusModel'; export * from './models/IIntegrationHistoryModel'; export * from './models/IIntegrationsModel'; diff --git a/packages/model-typings/src/models/IFreeSwitchChannelEventDeltaModel.ts b/packages/model-typings/src/models/IFreeSwitchChannelEventDeltaModel.ts deleted file mode 100644 index d460721c5d273..0000000000000 --- a/packages/model-typings/src/models/IFreeSwitchChannelEventDeltaModel.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { IFreeSwitchChannelEventDelta } from '@rocket.chat/core-typings'; -import type { WithoutId, InsertOneResult } from 'mongodb'; - -import type { IBaseModel, InsertionModel } from './IBaseModel'; - -export interface IFreeSwitchChannelEventDeltaModel extends IBaseModel { - registerDelta(channel: InsertionModel>): Promise>; -} diff --git a/packages/model-typings/src/models/IFreeSwitchChannelEventModel.ts b/packages/model-typings/src/models/IFreeSwitchChannelEventModel.ts deleted file mode 100644 index 5fbe805a08100..0000000000000 --- a/packages/model-typings/src/models/IFreeSwitchChannelEventModel.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { IFreeSwitchChannelEvent } from '@rocket.chat/core-typings'; -import type { FindCursor, FindOptions, WithoutId, InsertOneResult } from 'mongodb'; - -import type { IBaseModel, InsertionModel } from './IBaseModel'; - -export interface IFreeSwitchChannelEventModel extends IBaseModel { - registerEvent(event: WithoutId>): Promise>; - findAllByChannelUniqueId( - uniqueId: string, - options?: FindOptions, - ): FindCursor; -} diff --git a/packages/model-typings/src/models/IFreeSwitchChannelModel.ts b/packages/model-typings/src/models/IFreeSwitchChannelModel.ts deleted file mode 100644 index fe30b61dbf5e0..0000000000000 --- a/packages/model-typings/src/models/IFreeSwitchChannelModel.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { IFreeSwitchChannel } from '@rocket.chat/core-typings'; -import type { AggregateOptions, CountDocumentsOptions, FindCursor, FindOptions, WithoutId, InsertOneResult } from 'mongodb'; - -import type { IBaseModel, InsertionModel } from './IBaseModel'; - -export interface IFreeSwitchChannelModel extends IBaseModel { - registerChannel(channel: WithoutId>): Promise>; - findAllByUniqueIds(uniqueIds: string[], options?: FindOptions): FindCursor; - - countChannelsByKind(kind: Required['kind'], minDate?: Date, options?: CountDocumentsOptions): Promise; - countChannelsByKindAndDirection( - kind: Required['kind'], - callDirection: Required['callDirection'], - minDate?: Date, - options?: CountDocumentsOptions, - ): Promise; - sumChannelsDurationByKind(kind: Required['kind'], minDate?: Date, options?: AggregateOptions): Promise; - countChannelsByKindAndSuccessState( - kind: Required['kind'], - success: boolean, - minDate?: Date, - options?: CountDocumentsOptions, - ): Promise; -} diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index b2a3bdf86184b..8d04a8280ca4a 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -418,13 +418,6 @@ export interface IUsersModel extends IBaseModel { findAgentsAvailableWithoutBusinessHours(userIds?: IUser['_id'][]): FindCursor>; updateLivechatStatusByAgentIds(userIds: string[], status: ILivechatAgentStatus): Promise; findOneByFreeSwitchExtension(freeSwitchExtension: string, options?: FindOptions): Promise; - findOneByFreeSwitchExtensions( - freeSwitchExtensions: string[], - options?: FindOptions, - ): Promise; - setFreeSwitchExtension(userId: string, extension: string | undefined): Promise; - findAssignedFreeSwitchExtensions(): FindCursor; - findUsersWithAssignedFreeSwitchExtensions(options?: FindOptions): FindCursor; countUsersInRoles(roles: IRole['_id'][]): Promise; countAllUsersWithPendingAvatar(): Promise; findOneByIdAndRole(userId: IUser['_id'], role: string, options: FindOptions): Promise; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 903dc6998c097..c0d907f3aa010 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -20,8 +20,6 @@ import type { IExportOperationsModel, IFederationKeysModel, IFederationServersModel, - IFreeSwitchChannelModel, - IFreeSwitchChannelEventModel, IInstanceStatusModel, IIntegrationHistoryModel, IIntegrationsModel, @@ -85,7 +83,6 @@ import type { IMigrationsModel, IModerationReportsModel, IWorkspaceCredentialsModel, - IFreeSwitchChannelEventDeltaModel, IMediaCallsModel, IMediaCallChannelsModel, IMediaCallNegotiationsModel, @@ -150,9 +147,6 @@ export const ExportOperations = proxify('IExportOperatio export const FederationServers = proxify('IFederationServersModel'); export const FederationKeys = proxify('IFederationKeysModel'); export const FederationRoomEvents = proxify('IFederationRoomEventsModel'); -export const FreeSwitchChannel = proxify('IFreeSwitchChannelModel'); -export const FreeSwitchChannelEvent = proxify('IFreeSwitchChannelEventModel'); -export const FreeSwitchChannelEventDelta = proxify('IFreeSwitchChannelEventDeltaModel'); export const ImportData = proxify('IImportDataModel'); export const Imports = proxify('IImportsModel'); export const InstanceStatus = proxify('IInstanceStatusModel'); diff --git a/packages/models/src/modelClasses.ts b/packages/models/src/modelClasses.ts index 40b8bcc52d9bf..59f8b85cb1746 100644 --- a/packages/models/src/modelClasses.ts +++ b/packages/models/src/modelClasses.ts @@ -14,9 +14,6 @@ export * from './models/EmailInbox'; export * from './models/EmailMessageHistory'; export * from './models/EmojiCustom'; export * from './models/ExportOperations'; -export * from './models/FreeSwitchChannel'; -export * from './models/FreeSwitchChannelEvent'; -export * from './models/FreeSwitchChannelEventDelta'; export * from './models/FederationKeys'; export * from './models/FederationRoomEvents'; export * from './models/FederationServers'; diff --git a/packages/models/src/models/FreeSwitchChannel.ts b/packages/models/src/models/FreeSwitchChannel.ts deleted file mode 100644 index 992d68a018948..0000000000000 --- a/packages/models/src/models/FreeSwitchChannel.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { IFreeSwitchChannel, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; -import type { IFreeSwitchChannelModel, InsertionModel } from '@rocket.chat/model-typings'; -import type { - AggregateOptions, - Collection, - CountDocumentsOptions, - Db, - FindCursor, - FindOptions, - IndexDescription, - WithoutId, - InsertOneResult, -} from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; -import { readSecondaryPreferred } from '../readSecondaryPreferred'; - -export class FreeSwitchChannelRaw extends BaseRaw implements IFreeSwitchChannelModel { - constructor(db: Db, trash?: Collection>) { - super(db, 'freeswitch_channels', trash); - } - - protected override modelIndexes(): IndexDescription[] { - return [ - { key: { uniqueId: 1 }, unique: true }, - { key: { kind: 1, startedAt: -1 } }, - { key: { kind: 1, callDirection: 1, startedAt: -1 } }, - { key: { kind: 1, anyBridge: 1, startedAt: -1 } }, - ]; - } - - public async registerChannel(channel: WithoutId>): Promise> { - return this.insertOne(channel); - } - - public findAllByUniqueIds(uniqueIds: string[], options?: FindOptions): FindCursor { - return this.find( - { - uniqueId: { $in: uniqueIds }, - }, - options, - ); - } - - public countChannelsByKind(kind: Required['kind'], minDate?: Date, options?: CountDocumentsOptions): Promise { - return this.countDocuments( - { - kind, - ...(minDate && { startedAt: { $gte: minDate } }), - }, - { readPreference: readSecondaryPreferred(), ...options }, - ); - } - - public countChannelsByKindAndDirection( - kind: Required['kind'], - callDirection: Required['callDirection'], - minDate?: Date, - options?: CountDocumentsOptions, - ): Promise { - return this.countDocuments( - { - kind, - callDirection, - ...(minDate && { startedAt: { $gte: minDate } }), - }, - { readPreference: readSecondaryPreferred(), ...options }, - ); - } - - public async sumChannelsDurationByKind( - kind: Required['kind'], - minDate?: Date, - options?: AggregateOptions, - ): Promise { - return this.col - .aggregate( - [ - { - $match: { - kind, - ...(minDate && { startedAt: { $gte: minDate } }), - }, - }, - { - $group: { - _id: '1', - calls: { $sum: '$totalDuration' }, - }, - }, - ], - { readPreference: readSecondaryPreferred(), ...options }, - ) - .toArray() - .then(([{ calls }]) => calls); - } - - public countChannelsByKindAndSuccessState( - kind: Required['kind'], - success: boolean, - minDate?: Date, - options?: CountDocumentsOptions, - ): Promise { - return this.countDocuments( - { - kind, - anyBridge: success, - ...(minDate && { startedAt: { $gte: minDate } }), - }, - { readPreference: readSecondaryPreferred(), ...options }, - ); - } -} diff --git a/packages/models/src/models/FreeSwitchChannelEvent.ts b/packages/models/src/models/FreeSwitchChannelEvent.ts deleted file mode 100644 index cfb44649f1192..0000000000000 --- a/packages/models/src/models/FreeSwitchChannelEvent.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { IFreeSwitchChannelEvent, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; -import type { IFreeSwitchChannelEventModel, InsertionModel } from '@rocket.chat/model-typings'; -import type { IndexDescription, Collection, Db, FindOptions, FindCursor, WithoutId, InsertOneResult } from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; - -export class FreeSwitchChannelEventRaw extends BaseRaw implements IFreeSwitchChannelEventModel { - constructor(db: Db, trash?: Collection>) { - super(db, 'freeswitch_channel_events', trash); - } - - protected override modelIndexes(): IndexDescription[] { - return [ - { key: { channelUniqueId: 1, sequence: 1 }, unique: true }, - // Allow 3 days of events to be saved - { key: { receivedAt: 1 }, expireAfterSeconds: 3 * 24 * 60 * 60 }, - ]; - } - - public async registerEvent(event: WithoutId>): Promise> { - return this.insertOne(event); - } - - public findAllByChannelUniqueId( - channelUniqueId: string, - options?: FindOptions, - ): FindCursor { - const theOptions: FindOptions = { - sort: { - sequence: 1, - }, - ...options, - }; - - return this.find( - { - channelUniqueId, - }, - theOptions, - ); - } -} diff --git a/packages/models/src/models/FreeSwitchChannelEventDelta.ts b/packages/models/src/models/FreeSwitchChannelEventDelta.ts deleted file mode 100644 index be4451d40417e..0000000000000 --- a/packages/models/src/models/FreeSwitchChannelEventDelta.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { IFreeSwitchChannelEventDelta, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; -import type { IFreeSwitchChannelEventDeltaModel, InsertionModel } from '@rocket.chat/model-typings'; -import type { IndexDescription, Collection, Db, WithoutId, InsertOneResult } from 'mongodb'; - -import { BaseRaw } from './BaseRaw'; - -export class FreeSwitchChannelEventDeltaRaw extends BaseRaw implements IFreeSwitchChannelEventDeltaModel { - constructor(db: Db, trash?: Collection>) { - super(db, 'freeswitch_channel_event_deltas', trash); - } - - protected override modelIndexes(): IndexDescription[] { - return [ - { key: { channelUniqueId: 1, sequence: 1 }, unique: true }, - - // Keep event deltas for 30 days - { key: { _updatedAt: 1 }, expireAfterSeconds: 30 * 24 * 60 * 60 }, - ]; - } - - public async registerDelta( - delta: WithoutId>, - ): Promise> { - return this.insertOne(delta); - } -} diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index cb156f51f14a4..123dcab12ccb6 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -2688,34 +2688,6 @@ export class UsersRaw extends BaseRaw> implements IU ); } - findOneByFreeSwitchExtensions(freeSwitchExtensions: string[], options: FindOptions = {}) { - return this.findOne( - { - freeSwitchExtension: { $in: freeSwitchExtensions }, - }, - options, - ); - } - - findAssignedFreeSwitchExtensions() { - return this.findUsersWithAssignedFreeSwitchExtensions({ - projection: { - freeSwitchExtension: 1, - }, - }).map(({ freeSwitchExtension }) => freeSwitchExtension); - } - - findUsersWithAssignedFreeSwitchExtensions(options: FindOptions = {}) { - return this.find( - { - freeSwitchExtension: { - $exists: true, - }, - }, - options, - ); - } - // UPDATE addImportIds(_id: IUser['_id'], importIds: string[]) { importIds = ([] as string[]).concat(importIds); @@ -3140,17 +3112,6 @@ export class UsersRaw extends BaseRaw> implements IU ); } - async setFreeSwitchExtension(_id: IUser['_id'], extension?: string) { - return this.updateOne( - { - _id, - }, - { - ...(extension ? { $set: { freeSwitchExtension: extension } } : { $unset: { freeSwitchExtension: 1 } }), - }, - ); - } - // INSERT create(data: InsertionModel) { const user = { diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 256da9f7900a1..a7019029529c9 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -43,7 +43,6 @@ import type { SubscriptionsEndpoints } from './v1/subscriptionsEndpoints'; import type { TeamsEndpoints } from './v1/teams'; import type { UsersEndpoints } from './v1/users'; import type { VideoConferenceEndpoints } from './v1/videoConference'; -import type { VoipFreeSwitchEndpoints } from './v1/voip-freeswitch'; // eslint-disable-next-line @typescript-eslint/naming-convention export interface Endpoints @@ -90,7 +89,6 @@ export interface Endpoints CalendarEndpoints, AuthEndpoints, ImportEndpoints, - VoipFreeSwitchEndpoints, ServerEventsEndpoints, DefaultEndpoints {} @@ -248,7 +246,6 @@ export * from './v1/e2e/e2eSetUserPublicAndPrivateKeysParamsPOST'; export * from './v1/e2e/e2eUpdateGroupKeyParamsPOST'; export * from './v1/e2e'; export * from './v1/import'; -export * from './v1/voip-freeswitch'; export * from './v1/email-inbox'; export * from './v1/calendar'; export * from './v1/federation'; diff --git a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionAssignProps.ts b/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionAssignProps.ts deleted file mode 100644 index 7fcaf6c6a9a18..0000000000000 --- a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionAssignProps.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { JSONSchemaType } from 'ajv'; -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type VoipFreeSwitchExtensionAssignProps = { username: string; extension?: string }; - -const voipFreeSwitchExtensionAssignPropsSchema: JSONSchemaType = { - type: 'object', - properties: { - username: { - type: 'string', - nullable: false, - }, - extension: { - type: 'string', - nullable: true, - }, - }, - required: ['username'], - additionalProperties: false, -}; - -export const isVoipFreeSwitchExtensionAssignProps = ajv.compile(voipFreeSwitchExtensionAssignPropsSchema); diff --git a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetDetailsProps.ts b/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetDetailsProps.ts deleted file mode 100644 index a41ab7cd562a3..0000000000000 --- a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetDetailsProps.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { JSONSchemaType } from 'ajv'; -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type VoipFreeSwitchExtensionGetDetailsProps = { - extension: string; - group?: string; -}; - -const voipFreeSwitchExtensionGetDetailsPropsSchema: JSONSchemaType = { - type: 'object', - properties: { - extension: { - type: 'string', - nullable: false, - }, - group: { - type: 'string', - nullable: true, - }, - }, - required: ['extension'], - additionalProperties: false, -}; - -export const isVoipFreeSwitchExtensionGetDetailsProps = ajv.compile(voipFreeSwitchExtensionGetDetailsPropsSchema); diff --git a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetInfoProps.ts b/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetInfoProps.ts deleted file mode 100644 index 1ff871296757c..0000000000000 --- a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionGetInfoProps.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { JSONSchemaType } from 'ajv'; -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type VoipFreeSwitchExtensionGetInfoProps = { - userId: string; -}; - -const voipFreeSwitchExtensionGetInfoPropsSchema: JSONSchemaType = { - type: 'object', - properties: { - userId: { - type: 'string', - nullable: false, - }, - }, - required: ['userId'], - additionalProperties: false, -}; - -export const isVoipFreeSwitchExtensionGetInfoProps = ajv.compile(voipFreeSwitchExtensionGetInfoPropsSchema); diff --git a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionListProps.ts b/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionListProps.ts deleted file mode 100644 index e1c010afb3262..0000000000000 --- a/packages/rest-typings/src/v1/voip-freeswitch/VoipFreeSwitchExtensionListProps.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { JSONSchemaType } from 'ajv'; -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type VoipFreeSwitchExtensionListProps = { - username?: string; - type?: 'available' | 'free' | 'allocated' | 'all'; -}; - -const voipFreeSwitchExtensionListPropsSchema: JSONSchemaType = { - type: 'object', - properties: { - username: { - type: 'string', - nullable: true, - }, - type: { - type: 'string', - enum: ['available', 'free', 'allocated', 'all'], - nullable: true, - }, - }, - required: [], - additionalProperties: false, -}; - -export const isVoipFreeSwitchExtensionListProps = ajv.compile(voipFreeSwitchExtensionListPropsSchema); diff --git a/packages/rest-typings/src/v1/voip-freeswitch/index.ts b/packages/rest-typings/src/v1/voip-freeswitch/index.ts deleted file mode 100644 index 013e4e0351cbb..0000000000000 --- a/packages/rest-typings/src/v1/voip-freeswitch/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { FreeSwitchExtension } from '@rocket.chat/core-typings'; - -import type { VoipFreeSwitchExtensionAssignProps } from './VoipFreeSwitchExtensionAssignProps'; -import type { VoipFreeSwitchExtensionGetDetailsProps } from './VoipFreeSwitchExtensionGetDetailsProps'; -import type { VoipFreeSwitchExtensionGetInfoProps } from './VoipFreeSwitchExtensionGetInfoProps'; -import type { VoipFreeSwitchExtensionListProps } from './VoipFreeSwitchExtensionListProps'; - -export * from './VoipFreeSwitchExtensionAssignProps'; -export * from './VoipFreeSwitchExtensionGetDetailsProps'; -export * from './VoipFreeSwitchExtensionGetInfoProps'; -export * from './VoipFreeSwitchExtensionListProps'; - -export type VoipFreeSwitchEndpoints = { - '/v1/voip-freeswitch.extension.list': { - GET: (params: VoipFreeSwitchExtensionListProps) => { extensions: FreeSwitchExtension[] }; - }; - '/v1/voip-freeswitch.extension.getDetails': { - GET: (params: VoipFreeSwitchExtensionGetDetailsProps) => FreeSwitchExtension & { userId?: string; username?: string; name?: string }; - }; - '/v1/voip-freeswitch.extension.assign': { - POST: (params: VoipFreeSwitchExtensionAssignProps) => void; - }; - '/v1/voip-freeswitch.extension.getRegistrationInfoByUserId': { - GET: (params: VoipFreeSwitchExtensionGetInfoProps) => { - extension: FreeSwitchExtension; - credentials: { password: string; websocketPath: string }; - }; - }; -}; diff --git a/yarn.lock b/yarn.lock index ee59df1d8fb3b..651ae240b3b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8540,7 +8540,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/freeswitch@workspace:^, @rocket.chat/freeswitch@workspace:packages/freeswitch": +"@rocket.chat/freeswitch@workspace:packages/freeswitch": version: 0.0.0-use.local resolution: "@rocket.chat/freeswitch@workspace:packages/freeswitch" dependencies: @@ -9188,7 +9188,6 @@ __metadata: "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" "@rocket.chat/federation-sdk": "npm:0.3.2" - "@rocket.chat/freeswitch": "workspace:^" "@rocket.chat/fuselage": "npm:^0.69.0" "@rocket.chat/fuselage-forms": "npm:~0.1.1" "@rocket.chat/fuselage-hooks": "npm:~0.38.1" @@ -10639,7 +10638,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.3 - "@rocket.chat/ui-contexts": 25.0.0 + "@rocket.chat/ui-contexts": 25.0.1 "@tanstack/react-query": "*" react: "*" react-hook-form: "*"