1- import { execSync } from 'child_process' ;
2- import EventEmitter2 from 'eventemitter2' ;
3- import { opendirSync , readdirSync , rmSync } from 'fs' ;
4- import { Db } from 'mongodb' ;
5- import { join } from 'path' ;
6-
7- import { Auth , ConfigService , Database , DelInstance , HttpServer , Redis } from '../../config/env.config' ;
8- import { Logger } from '../../config/logger.config' ;
9- import { INSTANCE_DIR , STORE_DIR } from '../../config/path.config' ;
10- import { dbserver } from '../../libs/db.connect' ;
11- import { RedisCache } from '../../libs/redis.client' ;
12- import {
13- AuthModel ,
14- ChatwootModel ,
15- ContactModel ,
16- MessageModel ,
17- MessageUpModel ,
18- SettingsModel ,
19- WebhookModel ,
20- } from '../models' ;
21- import { RepositoryBroker } from '../repository/repository.manager' ;
22- import { WAStartupService } from './whatsapp.service' ;
23-
24- export class WAMonitoringService {
25- constructor (
26- private readonly eventEmitter : EventEmitter2 ,
27- private readonly configService : ConfigService ,
28- private readonly repository : RepositoryBroker ,
29- private readonly cache : RedisCache ,
30- ) {
31- this . logger . verbose ( 'instance created' ) ;
32-
33- this . removeInstance ( ) ;
34- this . noConnection ( ) ;
35- this . delInstanceFiles ( ) ;
36-
37- Object . assign ( this . db , configService . get < Database > ( 'DATABASE' ) ) ;
38- Object . assign ( this . redis , configService . get < Redis > ( 'REDIS' ) ) ;
39-
40- this . dbInstance = this . db . ENABLED
41- ? this . repository . dbServer ?. db ( this . db . CONNECTION . DB_PREFIX_NAME + '-instances' )
42- : undefined ;
43- }
44-
45- private readonly db : Partial < Database > = { } ;
46- private readonly redis : Partial < Redis > = { } ;
47-
48- private dbInstance : Db ;
49-
50- private dbStore = dbserver ;
51-
52- private readonly logger = new Logger ( WAMonitoringService . name ) ;
53- public readonly waInstances : Record < string , WAStartupService > = { } ;
54-
55- public delInstanceTime ( instance : string ) {
56- const time = this . configService . get < DelInstance > ( 'DEL_INSTANCE' ) ;
57- if ( typeof time === 'number' && time > 0 ) {
58- this . logger . verbose ( `Instance "${ instance } " don't have connection, will be removed in ${ time } minutes` ) ;
59-
60- setTimeout ( async ( ) => {
61- if ( this . waInstances [ instance ] ?. connectionStatus ?. state !== 'open' ) {
62- if ( this . waInstances [ instance ] ?. connectionStatus ?. state === 'connecting' ) {
63- await this . waInstances [ instance ] ?. client ?. logout ( 'Log out instance: ' + instance ) ;
64- this . waInstances [ instance ] ?. client ?. ws ?. close ( ) ;
65- this . waInstances [ instance ] ?. client ?. end ( undefined ) ;
66- delete this . waInstances [ instance ] ;
67- } else {
68- delete this . waInstances [ instance ] ;
69- this . eventEmitter . emit ( 'remove.instance' , instance , 'inner' ) ;
70- }
71- }
72- } , 1000 * 60 * time ) ;
73- }
74- }
75-
761 public async instanceInfo ( instanceName ?: string) {
772 this . logger . verbose ( 'get instance info' ) ;
783
794 const urlServer = this . configService . get < HttpServer > ( 'SERVER' ) . URL ;
805
816 const instances : any [ ] = await Promise . all (
827 Object . entries ( this . waInstances ) . map ( async ( [ key , value ] ) => {
83- if ( ! value || ! value . connectionStatus || ! value . connectionStatus . state ) {
84- return {
85- instance : {
86- instanceName : key ,
87- status : value ?. connectionStatus ?. state || 'unknown' ,
88- } ,
89- } ;
8+ const status = value ?. connectionStatus ?. state || 'unknown' ;
9+
10+ if ( status === 'unknown' ) {
11+ return null ;
9012 }
9113
92- this . logger . verbose ( 'instance: ' + key + ' - connectionStatus: open' ) ;
14+ if ( status === 'open' ) {
15+ this . logger . verbose ( 'instance: ' + key + ' - connectionStatus: open' ) ;
16+ }
9317
9418 const instanceData : any = {
9519 instance : {
@@ -98,7 +22,7 @@ export class WAMonitoringService {
9822 profileName : ( await value . getProfileName ( ) ) || 'not loaded' ,
9923 profilePictureUrl : value . profilePictureUrl ,
10024 profileStatus : ( await value . getProfileStatus ( ) ) || '' ,
101- status : 'open' ,
25+ status : status ,
10226 } ,
10327 } ;
10428
@@ -117,7 +41,7 @@ export class WAMonitoringService {
11741
11842 return instanceData ;
11943 } ) ,
120- ) ;
44+ ) . then ( ( results ) => results . filter ( ( instance ) => instance !== null ) ) ;
12145
12246 this . logger . verbose ( 'return instance info: ' + instances . length ) ;
12347
@@ -128,231 +52,3 @@ export class WAMonitoringService {
12852
12953 return instances ;
13054 }
131-
132- private delInstanceFiles ( ) {
133- this . logger . verbose ( 'cron to delete instance files started' ) ;
134- setInterval ( async ( ) => {
135- if ( this . db . ENABLED && this . db . SAVE_DATA . INSTANCE ) {
136- const collections = await this . dbInstance . collections ( ) ;
137- collections . forEach ( async ( collection ) => {
138- const name = collection . namespace . replace ( / ^ [ \w - ] + ./ , '' ) ;
139- await this . dbInstance . collection ( name ) . deleteMany ( {
140- $or : [ { _id : { $regex : / ^ a p p .s t a t e .* / } } , { _id : { $regex : / ^ s e s s i o n - .* / } } ] ,
141- } ) ;
142- this . logger . verbose ( 'instance files deleted: ' + name ) ;
143- } ) ;
144- } else if ( ! this . redis . ENABLED ) {
145- const dir = opendirSync ( INSTANCE_DIR , { encoding : 'utf-8' } ) ;
146- for await ( const dirent of dir ) {
147- if ( dirent . isDirectory ( ) ) {
148- const files = readdirSync ( join ( INSTANCE_DIR , dirent . name ) , {
149- encoding : 'utf-8' ,
150- } ) ;
151- files . forEach ( async ( file ) => {
152- if ( file . match ( / ^ a p p .s t a t e .* / ) || file . match ( / ^ s e s s i o n - .* / ) ) {
153- rmSync ( join ( INSTANCE_DIR , dirent . name , file ) , {
154- recursive : true ,
155- force : true ,
156- } ) ;
157- }
158- } ) ;
159- this . logger . verbose ( 'instance files deleted: ' + dirent . name ) ;
160- }
161- }
162- }
163- } , 3600 * 1000 * 2 ) ;
164- }
165-
166- public async cleaningUp ( instanceName : string ) {
167- this . logger . verbose ( 'cleaning up instance: ' + instanceName ) ;
168- if ( this . db . ENABLED && this . db . SAVE_DATA . INSTANCE ) {
169- this . logger . verbose ( 'cleaning up instance in database: ' + instanceName ) ;
170- await this . repository . dbServer . connect ( ) ;
171- const collections : any [ ] = await this . dbInstance . collections ( ) ;
172- if ( collections . length > 0 ) {
173- await this . dbInstance . dropCollection ( instanceName ) ;
174- }
175- return ;
176- }
177-
178- if ( this . redis . ENABLED ) {
179- this . logger . verbose ( 'cleaning up instance in redis: ' + instanceName ) ;
180- this . cache . reference = instanceName ;
181- await this . cache . delAll ( ) ;
182- return ;
183- }
184-
185- this . logger . verbose ( 'cleaning up instance in files: ' + instanceName ) ;
186- rmSync ( join ( INSTANCE_DIR , instanceName ) , { recursive : true , force : true } ) ;
187- }
188-
189- public async cleaningStoreFiles ( instanceName : string ) {
190- if ( ! this . db . ENABLED ) {
191- this . logger . verbose ( 'cleaning store files instance: ' + instanceName ) ;
192- rmSync ( join ( INSTANCE_DIR , instanceName ) , { recursive : true , force : true } ) ;
193-
194- execSync ( `rm -rf ${ join ( STORE_DIR , 'chats' , instanceName ) } ` ) ;
195- execSync ( `rm -rf ${ join ( STORE_DIR , 'contacts' , instanceName ) } ` ) ;
196- execSync ( `rm -rf ${ join ( STORE_DIR , 'message-up' , instanceName ) } ` ) ;
197- execSync ( `rm -rf ${ join ( STORE_DIR , 'messages' , instanceName ) } ` ) ;
198-
199- execSync ( `rm -rf ${ join ( STORE_DIR , 'auth' , 'apikey' , instanceName + '.json' ) } ` ) ;
200- execSync ( `rm -rf ${ join ( STORE_DIR , 'webhook' , instanceName + '.json' ) } ` ) ;
201- execSync ( `rm -rf ${ join ( STORE_DIR , 'chatwoot' , instanceName + '*' ) } ` ) ;
202- execSync ( `rm -rf ${ join ( STORE_DIR , 'chamaai' , instanceName + '*' ) } ` ) ;
203- execSync ( `rm -rf ${ join ( STORE_DIR , 'proxy' , instanceName + '*' ) } ` ) ;
204- execSync ( `rm -rf ${ join ( STORE_DIR , 'rabbitmq' , instanceName + '*' ) } ` ) ;
205- execSync ( `rm -rf ${ join ( STORE_DIR , 'typebot' , instanceName + '*' ) } ` ) ;
206- execSync ( `rm -rf ${ join ( STORE_DIR , 'websocket' , instanceName + '*' ) } ` ) ;
207- execSync ( `rm -rf ${ join ( STORE_DIR , 'settings' , instanceName + '*' ) } ` ) ;
208-
209- return ;
210- }
211-
212- this . logger . verbose ( 'cleaning store database instance: ' + instanceName ) ;
213-
214- await AuthModel . deleteMany ( { owner : instanceName } ) ;
215- await ContactModel . deleteMany ( { owner : instanceName } ) ;
216- await MessageModel . deleteMany ( { owner : instanceName } ) ;
217- await MessageUpModel . deleteMany ( { owner : instanceName } ) ;
218- await AuthModel . deleteMany ( { _id : instanceName } ) ;
219- await WebhookModel . deleteMany ( { _id : instanceName } ) ;
220- await ChatwootModel . deleteMany ( { _id : instanceName } ) ;
221- await SettingsModel . deleteMany ( { _id : instanceName } ) ;
222-
223- return ;
224- }
225-
226- public async loadInstance ( ) {
227- this . logger . verbose ( 'Loading instances' ) ;
228-
229- try {
230- if ( this . redis . ENABLED ) {
231- await this . loadInstancesFromRedis ( ) ;
232- } else if ( this . db . ENABLED && this . db . SAVE_DATA . INSTANCE ) {
233- await this . loadInstancesFromDatabase ( ) ;
234- } else {
235- await this . loadInstancesFromFiles ( ) ;
236- }
237- } catch ( error ) {
238- this . logger . error ( error ) ;
239- }
240- }
241-
242- private async setInstance ( name : string ) {
243- const instance = new WAStartupService ( this . configService , this . eventEmitter , this . repository , this . cache ) ;
244- instance . instanceName = name ;
245- this . logger . verbose ( 'Instance loaded: ' + name ) ;
246-
247- await instance . connectToWhatsapp ( ) ;
248- this . logger . verbose ( 'connectToWhatsapp: ' + name ) ;
249-
250- this . waInstances [ name ] = instance ;
251- }
252-
253- private async loadInstancesFromRedis ( ) {
254- this . logger . verbose ( 'Redis enabled' ) ;
255- await this . cache . connect ( this . redis as Redis ) ;
256- const keys = await this . cache . instanceKeys ( ) ;
257-
258- if ( keys ?. length > 0 ) {
259- this . logger . verbose ( 'Reading instance keys and setting instances' ) ;
260- await Promise . all ( keys . map ( ( k ) => this . setInstance ( k . split ( ':' ) [ 1 ] ) ) ) ;
261- } else {
262- this . logger . verbose ( 'No instance keys found' ) ;
263- }
264- }
265-
266- private async loadInstancesFromDatabase ( ) {
267- this . logger . verbose ( 'Database enabled' ) ;
268- await this . repository . dbServer . connect ( ) ;
269- const collections : any [ ] = await this . dbInstance . collections ( ) ;
270-
271- if ( collections . length > 0 ) {
272- this . logger . verbose ( 'Reading collections and setting instances' ) ;
273- await Promise . all ( collections . map ( ( coll ) => this . setInstance ( coll . namespace . replace ( / ^ [ \w - ] + \. / , '' ) ) ) ) ;
274- } else {
275- this . logger . verbose ( 'No collections found' ) ;
276- }
277- }
278-
279- private async loadInstancesFromFiles ( ) {
280- this . logger . verbose ( 'Store in files enabled' ) ;
281- const dir = opendirSync ( INSTANCE_DIR , { encoding : 'utf-8' } ) ;
282- const instanceDirs = [ ] ;
283-
284- for await ( const dirent of dir ) {
285- if ( dirent . isDirectory ( ) ) {
286- instanceDirs . push ( dirent . name ) ;
287- } else {
288- this . logger . verbose ( 'No instance files found' ) ;
289- }
290- }
291-
292- await Promise . all (
293- instanceDirs . map ( async ( instanceName ) => {
294- this . logger . verbose ( 'Reading instance files and setting instances: ' + instanceName ) ;
295- const files = readdirSync ( join ( INSTANCE_DIR , instanceName ) , { encoding : 'utf-8' } ) ;
296-
297- if ( files . length === 0 ) {
298- rmSync ( join ( INSTANCE_DIR , instanceName ) , { recursive : true , force : true } ) ;
299- } else {
300- await this . setInstance ( instanceName ) ;
301- }
302- } ) ,
303- ) ;
304- }
305-
306- private removeInstance ( ) {
307- this . eventEmitter . on ( 'remove.instance' , async ( instanceName : string ) => {
308- this . logger . verbose ( 'remove instance: ' + instanceName ) ;
309- try {
310- this . logger . verbose ( 'instance: ' + instanceName + ' - removing from memory' ) ;
311- this . waInstances [ instanceName ] = undefined ;
312- } catch ( error ) {
313- this . logger . error ( error ) ;
314- }
315-
316- try {
317- this . logger . verbose ( 'request cleaning up instance: ' + instanceName ) ;
318- this . cleaningUp ( instanceName ) ;
319- this . cleaningStoreFiles ( instanceName ) ;
320- } finally {
321- this . logger . warn ( `Instance "${ instanceName } " - REMOVED` ) ;
322- }
323- } ) ;
324- this . eventEmitter . on ( 'logout.instance' , async ( instanceName : string ) => {
325- this . logger . verbose ( 'logout instance: ' + instanceName ) ;
326- try {
327- this . logger . verbose ( 'request cleaning up instance: ' + instanceName ) ;
328- this . cleaningUp ( instanceName ) ;
329- } finally {
330- this . logger . warn ( `Instance "${ instanceName } " - LOGOUT` ) ;
331- }
332- } ) ;
333- }
334-
335- private noConnection ( ) {
336- this . logger . verbose ( 'checking instances without connection' ) ;
337- this . eventEmitter . on ( 'no.connection' , async ( instanceName ) => {
338- try {
339- this . logger . verbose ( 'logging out instance: ' + instanceName ) ;
340- await this . waInstances [ instanceName ] ?. client ?. logout ( 'Log out instance: ' + instanceName ) ;
341-
342- this . logger . verbose ( 'close connection instance: ' + instanceName ) ;
343- this . waInstances [ instanceName ] ?. client ?. ws ?. close ( ) ;
344-
345- this . waInstances [ instanceName ] . instance . qrcode = { count : 0 } ;
346- this . waInstances [ instanceName ] . stateConnection . state = 'close' ;
347- } catch ( error ) {
348- this . logger . error ( {
349- localError : 'noConnection' ,
350- warn : 'Error deleting instance from memory.' ,
351- error,
352- } ) ;
353- } finally {
354- this . logger . warn ( `Instance "${ instanceName } " - NOT CONNECTION` ) ;
355- }
356- } ) ;
357- }
358- }
0 commit comments