Skip to content

Commit 99c5a59

Browse files
author
Hamza Shahid
committed
fix: auto-load instances from database when not in memory
1 parent 2e67c77 commit 99c5a59

3 files changed

Lines changed: 155 additions & 38 deletions

File tree

src/api/controllers/instance.controller.ts

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,62 @@ export class InstanceController {
3434

3535
private readonly logger = new Logger('InstanceController');
3636

37+
// Helper to get or load instance from database
38+
private async getOrLoadInstance(instanceName: string) {
39+
// Check if already in memory
40+
if (this.waMonitor.waInstances[instanceName]) {
41+
return this.waMonitor.waInstances[instanceName];
42+
}
43+
44+
// Try to load from database
45+
const dbInstance = await this.prismaRepository.instance.findFirst({
46+
where: { name: instanceName },
47+
});
48+
49+
if (!dbInstance) {
50+
return null;
51+
}
52+
53+
// Initialize the instance
54+
const instanceData: InstanceDto = {
55+
instanceName: dbInstance.name,
56+
instanceId: dbInstance.id,
57+
integration: dbInstance.integration as any,
58+
token: dbInstance.token,
59+
number: dbInstance.number,
60+
businessId: dbInstance.businessId,
61+
};
62+
63+
const instance = channelController.init(instanceData, {
64+
configService: this.configService,
65+
eventEmitter: this.eventEmitter,
66+
prismaRepository: this.prismaRepository,
67+
cache: this.cache,
68+
chatwootCache: this.chatwootCache,
69+
baileysCache: this.baileysCache,
70+
providerFiles: this.providerFiles,
71+
});
72+
73+
if (!instance) {
74+
return null;
75+
}
76+
77+
instance.setInstance({
78+
instanceId: dbInstance.id,
79+
instanceName: dbInstance.name,
80+
integration: dbInstance.integration as any,
81+
token: dbInstance.token,
82+
number: dbInstance.number,
83+
businessId: dbInstance.businessId,
84+
ownerJid: dbInstance.ownerJid,
85+
});
86+
87+
this.waMonitor.waInstances[instanceName] = instance;
88+
this.logger.info(`Instance "${instanceName}" loaded from database`);
89+
90+
return instance;
91+
}
92+
3793
public async createInstance(instanceData: InstanceDto) {
3894
try {
3995
const instance = channelController.init(instanceData, {
@@ -308,13 +364,15 @@ export class InstanceController {
308364

309365
public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) {
310366
try {
311-
const instance = this.waMonitor.waInstances[instanceName];
312-
const state = instance?.connectionStatus?.state;
367+
// Get instance from memory or load from database
368+
const instance = await this.getOrLoadInstance(instanceName);
313369

314-
if (!state) {
370+
if (!instance) {
315371
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
316372
}
317373

374+
const state = instance?.connectionStatus?.state;
375+
318376
if (state == 'open') {
319377
return await this.connectionState({ instanceName });
320378
}
@@ -323,20 +381,10 @@ export class InstanceController {
323381
return instance.qrCode;
324382
}
325383

326-
if (state == 'close') {
327-
await instance.connectToWhatsapp(number);
328-
329-
await delay(2000);
330-
return instance.qrCode;
331-
}
332-
333-
return {
334-
instance: {
335-
instanceName: instanceName,
336-
status: state,
337-
},
338-
qrcode: instance?.qrCode,
339-
};
384+
// Connect if closed or no state
385+
await instance.connectToWhatsapp(number);
386+
await delay(2000);
387+
return instance.qrCode;
340388
} catch (error) {
341389
this.logger.error(error);
342390
return { error: true, message: error.toString() };
@@ -345,13 +393,14 @@ export class InstanceController {
345393

346394
public async restartInstance({ instanceName }: InstanceDto) {
347395
try {
348-
const instance = this.waMonitor.waInstances[instanceName];
349-
const state = instance?.connectionStatus?.state;
396+
const instance = await this.getOrLoadInstance(instanceName);
350397

351-
if (!state) {
398+
if (!instance) {
352399
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
353400
}
354401

402+
const state = instance?.connectionStatus?.state;
403+
355404
if (state === 'close') {
356405
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
357406
}

src/api/controllers/sendMessage.controller.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,23 @@ function isEmoji(str: string) {
3131
export class SendMessageController {
3232
constructor(private readonly waMonitor: WAMonitoringService) {}
3333

34-
private getInstance(instanceName: string) {
35-
const instance = this.waMonitor.waInstances[instanceName];
34+
private async getInstance(instanceName: string) {
35+
// Try to get from memory or load from database
36+
const instance = await this.waMonitor.getOrLoadInstance(instanceName);
3637
if (!instance) {
37-
throw new NotFoundException(
38-
`Instance "${instanceName}" not found or not connected. Please connect the instance first.`,
39-
);
38+
throw new NotFoundException(`Instance "${instanceName}" not found. Please create the instance first.`);
4039
}
4140
return instance;
4241
}
4342

4443
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
45-
return await this.getInstance(instanceName).templateMessage(data);
44+
const instance = await this.getInstance(instanceName);
45+
return await instance.templateMessage(data);
4646
}
4747

4848
public async sendText({ instanceName }: InstanceDto, data: SendTextDto) {
49-
return await this.getInstance(instanceName).textMessage(data);
49+
const instance = await this.getInstance(instanceName);
50+
return await instance.textMessage(data);
5051
}
5152

5253
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto, file?: any) {
@@ -55,62 +56,73 @@ export class SendMessageController {
5556
}
5657

5758
if (file || isURL(data?.media) || isBase64(data?.media)) {
58-
return await this.getInstance(instanceName).mediaMessage(data, file);
59+
const instance = await this.getInstance(instanceName);
60+
return await instance.mediaMessage(data, file);
5961
}
6062
throw new BadRequestException('Owned media must be a url or base64');
6163
}
6264

6365
public async sendPtv({ instanceName }: InstanceDto, data: SendPtvDto, file?: any) {
6466
if (file || isURL(data?.video) || isBase64(data?.video)) {
65-
return await this.getInstance(instanceName).ptvMessage(data, file);
67+
const instance = await this.getInstance(instanceName);
68+
return await instance.ptvMessage(data, file);
6669
}
6770
throw new BadRequestException('Owned media must be a url or base64');
6871
}
6972

7073
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto, file?: any) {
7174
if (file || isURL(data.sticker) || isBase64(data.sticker)) {
72-
return await this.getInstance(instanceName).mediaSticker(data, file);
75+
const instance = await this.getInstance(instanceName);
76+
return await instance.mediaSticker(data, file);
7377
}
7478
throw new BadRequestException('Owned media must be a url or base64');
7579
}
7680

7781
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto, file?: any) {
7882
if (file?.buffer || isURL(data.audio) || isBase64(data.audio)) {
79-
return await this.getInstance(instanceName).audioWhatsapp(data, file);
83+
const instance = await this.getInstance(instanceName);
84+
return await instance.audioWhatsapp(data, file);
8085
} else {
8186
console.error('El archivo no tiene buffer o el audio no es una URL o Base64 válida');
8287
throw new BadRequestException('Owned media must be a url, base64, or valid file with buffer');
8388
}
8489
}
8590

8691
public async sendButtons({ instanceName }: InstanceDto, data: SendButtonsDto) {
87-
return await this.getInstance(instanceName).buttonMessage(data);
92+
const instance = await this.getInstance(instanceName);
93+
return await instance.buttonMessage(data);
8894
}
8995

9096
public async sendLocation({ instanceName }: InstanceDto, data: SendLocationDto) {
91-
return await this.getInstance(instanceName).locationMessage(data);
97+
const instance = await this.getInstance(instanceName);
98+
return await instance.locationMessage(data);
9299
}
93100

94101
public async sendList({ instanceName }: InstanceDto, data: SendListDto) {
95-
return await this.getInstance(instanceName).listMessage(data);
102+
const instance = await this.getInstance(instanceName);
103+
return await instance.listMessage(data);
96104
}
97105

98106
public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) {
99-
return await this.getInstance(instanceName).contactMessage(data);
107+
const instance = await this.getInstance(instanceName);
108+
return await instance.contactMessage(data);
100109
}
101110

102111
public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) {
103112
if (!isEmoji(data.reaction)) {
104113
throw new BadRequestException('Reaction must be a single emoji or empty string');
105114
}
106-
return await this.getInstance(instanceName).reactionMessage(data);
115+
const instance = await this.getInstance(instanceName);
116+
return await instance.reactionMessage(data);
107117
}
108118

109119
public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) {
110-
return await this.getInstance(instanceName).pollMessage(data);
120+
const instance = await this.getInstance(instanceName);
121+
return await instance.pollMessage(data);
111122
}
112123

113124
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto, file?: any) {
114-
return await this.getInstance(instanceName).statusMessage(data, file);
125+
const instance = await this.getInstance(instanceName);
126+
return await instance.statusMessage(data, file);
115127
}
116128
}

src/api/services/monitor.service.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,62 @@ export class WAMonitoringService {
4242

4343
private readonly providerSession: ProviderSession;
4444

45+
// Public helper to get instance from memory or load from database
46+
public async getOrLoadInstance(instanceName: string) {
47+
// Check if already in memory
48+
if (this.waInstances[instanceName]) {
49+
return this.waInstances[instanceName];
50+
}
51+
52+
// Try to load from database
53+
const dbInstance = await this.prismaRepository.instance.findFirst({
54+
where: { name: instanceName },
55+
});
56+
57+
if (!dbInstance) {
58+
return null;
59+
}
60+
61+
// Initialize the instance
62+
const instanceData: InstanceDto = {
63+
instanceName: dbInstance.name,
64+
instanceId: dbInstance.id,
65+
integration: dbInstance.integration as any,
66+
token: dbInstance.token,
67+
number: dbInstance.number,
68+
businessId: dbInstance.businessId,
69+
};
70+
71+
const instance = channelController.init(instanceData, {
72+
configService: this.configService,
73+
eventEmitter: this.eventEmitter,
74+
prismaRepository: this.prismaRepository,
75+
cache: this.cache,
76+
chatwootCache: this.chatwootCache,
77+
baileysCache: this.baileysCache,
78+
providerFiles: this.providerFiles,
79+
});
80+
81+
if (!instance) {
82+
return null;
83+
}
84+
85+
instance.setInstance({
86+
instanceId: dbInstance.id,
87+
instanceName: dbInstance.name,
88+
integration: dbInstance.integration as any,
89+
token: dbInstance.token,
90+
number: dbInstance.number,
91+
businessId: dbInstance.businessId,
92+
ownerJid: dbInstance.ownerJid,
93+
});
94+
95+
this.waInstances[instanceName] = instance;
96+
this.logger.info(`Instance "${instanceName}" loaded from database on-demand`);
97+
98+
return instance;
99+
}
100+
45101
public delInstanceTime(instance: string) {
46102
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
47103
if (typeof time === 'number' && time > 0) {

0 commit comments

Comments
 (0)