@@ -11,7 +11,14 @@ import type {
1111 AtLeast ,
1212} from '@rocket.chat/core-typings' ;
1313import { isKnownFreeSwitchEventType } from '@rocket.chat/core-typings' ;
14- import { getDomain , getUserPassword , getExtensionList , getExtensionDetails , listenToEvents } from '@rocket.chat/freeswitch' ;
14+ import {
15+ getDomain ,
16+ getUserPassword ,
17+ getExtensionList ,
18+ getExtensionDetails ,
19+ FreeSwitchEventClient ,
20+ type FreeSwitchOptions ,
21+ } from '@rocket.chat/freeswitch' ;
1522import type { InsertionModel } from '@rocket.chat/model-typings' ;
1623import { FreeSwitchCall , FreeSwitchEvent , Users } from '@rocket.chat/models' ;
1724import { objectMap , wrapExceptions } from '@rocket.chat/tools' ;
@@ -25,47 +32,135 @@ export class VoipFreeSwitchService extends ServiceClassInternal implements IVoip
2532
2633 private serviceStarter : ServiceStarter ;
2734
35+ private eventClient : FreeSwitchEventClient | null = null ;
36+
37+ private wasEverConnected = false ;
38+
2839 constructor ( ) {
2940 super ( ) ;
3041
31- this . serviceStarter = new ServiceStarter ( ( ) => this . startEvents ( ) ) ;
42+ this . serviceStarter = new ServiceStarter (
43+ async ( ) => {
44+ // Delay start to ensure setting values are up-to-date in the cache
45+ setImmediate ( ( ) => this . startEvents ( ) ) ;
46+ } ,
47+ async ( ) => this . stopEvents ( ) ,
48+ ) ;
3249 this . onEvent ( 'watch.settings' , async ( { setting } ) : Promise < void > => {
33- if ( setting . _id === 'VoIP_TeamCollab_Enabled' && setting . value === true ) {
34- void this . serviceStarter . start ( ) ;
50+ if ( setting . _id === 'VoIP_TeamCollab_Enabled' ) {
51+ if ( setting . value !== true ) {
52+ void this . serviceStarter . stop ( ) ;
53+ return ;
54+ }
55+
56+ if ( setting . value === true ) {
57+ void this . serviceStarter . start ( ) ;
58+ return ;
59+ }
60+ }
61+
62+ if ( setting . _id === 'VoIP_TeamCollab_FreeSwitch_Host' ) {
63+ // Re-connect if the host changes
64+ if ( this . eventClient && this . eventClient . host !== setting . value ) {
65+ this . stopEvents ( ) ;
66+ }
67+
68+ if ( setting . value ) {
69+ void this . serviceStarter . start ( ) ;
70+ }
71+ }
72+
73+ // If any other freeswitch setting changes, only reconnect if it's not yet connected
74+ if ( setting . _id . startsWith ( 'VoIP_TeamCollab_FreeSwitch_' ) ) {
75+ if ( ! this . eventClient ?. isReady ( ) ) {
76+ this . stopEvents ( ) ;
77+ void this . serviceStarter . start ( ) ;
78+ }
3579 }
3680 } ) ;
3781 }
3882
39- private listening = false ;
40-
4183 public async started ( ) : Promise < void > {
4284 void this . serviceStarter . start ( ) ;
4385 }
4486
4587 private async startEvents ( ) : Promise < void > {
46- if ( this . listening ) {
88+ if ( this . eventClient ) {
89+ if ( ! this . eventClient . isDone ( ) ) {
90+ return ;
91+ }
92+
93+ const client = this . eventClient ;
94+ this . eventClient = null ;
95+ client . endConnection ( ) ;
96+ }
97+
98+ const options = wrapExceptions ( ( ) => this . getConnectionSettings ( ) ) . suppress ( ) ;
99+ if ( ! options ) {
100+ this . wasEverConnected = false ;
47101 return ;
48102 }
49103
50- try {
51- // #ToDo: Reconnection
52- // #ToDo: Only connect from one rocket.chat instance
53- await listenToEvents (
54- async ( ...args ) => wrapExceptions ( ( ) => this . onFreeSwitchEvent ( ...args ) ) . suppress ( ) ,
55- this . getConnectionSettings ( ) ,
56- ) ;
57- this . listening = true ;
58- } catch ( _e ) {
59- this . listening = false ;
104+ this . initializeEventClient ( options ) ;
105+ }
106+
107+ private retryEventsLater ( ) : void {
108+ // Try to re-establish connection after some time
109+ setTimeout (
110+ ( ) => {
111+ void this . startEvents ( ) ;
112+ } ,
113+ this . wasEverConnected ? 3000 : 20_000 ,
114+ ) ;
115+ }
116+
117+ private initializeEventClient ( options : FreeSwitchOptions ) : void {
118+ const client = FreeSwitchEventClient . listenToEvents ( options ) ;
119+ this . eventClient = client ;
120+
121+ client . on ( 'ready' , ( ) => {
122+ if ( this . eventClient !== client ) {
123+ return ;
124+ }
125+ this . wasEverConnected = true ;
126+ } ) ;
127+
128+ client . on ( 'end' , ( ) => {
129+ if ( this . eventClient && this . eventClient !== client ) {
130+ return ;
131+ }
132+
133+ this . eventClient = null ;
134+ this . retryEventsLater ( ) ;
135+ } ) ;
136+
137+ client . on ( 'event' , async ( { eventName, eventData } ) => {
138+ if ( this . eventClient !== client ) {
139+ return ;
140+ }
141+
142+ await wrapExceptions ( ( ) =>
143+ this . onFreeSwitchEvent ( eventName as string , eventData as unknown as Record < string , string | undefined > ) ,
144+ ) . suppress ( ) ;
145+ } ) ;
146+ }
147+
148+ private stopEvents ( ) : void {
149+ if ( ! this . eventClient ) {
150+ return ;
60151 }
152+
153+ this . eventClient . endConnection ( ) ;
154+ this . wasEverConnected = false ;
155+ this . eventClient = null ;
61156 }
62157
63- private getConnectionSettings ( ) : { host : string ; port : number ; password : string ; timeout : number } {
64- if ( ! settings . get ( 'VoIP_TeamCollab_Enabled' ) && ! process . env . FREESWITCHIP ) {
158+ private getConnectionSettings ( ) : FreeSwitchOptions {
159+ if ( ! settings . get ( 'VoIP_TeamCollab_Enabled' ) ) {
65160 throw new Error ( 'VoIP is disabled.' ) ;
66161 }
67162
68- const host = process . env . FREESWITCHIP || settings . get < string > ( 'VoIP_TeamCollab_FreeSwitch_Host' ) ;
163+ const host = settings . get < string > ( 'VoIP_TeamCollab_FreeSwitch_Host' ) ;
69164 if ( ! host ) {
70165 throw new Error ( 'VoIP is not properly configured.' ) ;
71166 }
@@ -75,14 +170,16 @@ export class VoipFreeSwitchService extends ServiceClassInternal implements IVoip
75170 const password = settings . get < string > ( 'VoIP_TeamCollab_FreeSwitch_Password' ) ;
76171
77172 return {
78- host,
79- port,
173+ socketOptions : {
174+ host,
175+ port,
176+ } ,
80177 password,
81178 timeout,
82179 } ;
83180 }
84181
85- private async onFreeSwitchEvent ( eventName : string , data : Record < string , string | undefined > ) : Promise < void > {
182+ public async onFreeSwitchEvent ( eventName : string , data : Record < string , string | undefined > ) : Promise < void > {
86183 const uniqueId = data [ 'Unique-ID' ] ;
87184 if ( ! uniqueId ) {
88185 return ;
0 commit comments