@peopl-health/nexus 3.3.4 → 3.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,8 @@ const { Message } = require('../models/messageModel.js');
3
3
  const { ScheduledMessage: DefaultScheduledMessage } = require('../models/agendaMessageModel.js');
4
4
  const {
5
5
  sendMessage: defaultSendMessage,
6
- sendScheduledMessage: defaultSendScheduledMessage
6
+ sendScheduledMessage: defaultSendScheduledMessage,
7
+ getDefaultInstance
7
8
  } = require('../core/NexusMessaging');
8
9
  const { ensureWhatsAppFormat } = require('../helpers/twilioHelper');
9
10
  const { getRecordByFilter: defaultGetRecordByFilter } = require('../services/airtableService');
@@ -31,6 +32,12 @@ const ensureDependency = (res, condition, errorMessage) => {
31
32
  return false;
32
33
  };
33
34
 
35
+ const ensureDefaultInstance = (res) => ensureDependency(
36
+ res,
37
+ Boolean(getDefaultInstance()),
38
+ 'NexusMessaging default instance not initialized'
39
+ );
40
+
34
41
  const persistScheduledMessage = async (Model, payload) => {
35
42
  if (Model && typeof Model.create === 'function') {
36
43
  return await Model.create(payload);
@@ -80,6 +87,8 @@ const sendMessageController = async (req, res) => {
80
87
  const hasScheduler = typeof dependencies.sendScheduledMessage === 'function';
81
88
  const hasDirectSend = typeof dependencies.sendMessage === 'function';
82
89
  if (!ensureDependency(res, hasScheduler || hasDirectSend, 'No messaging provider configured. Ensure Nexus.initialize() completed before using the message controllers.')) return;
90
+ if ((dependencies.sendMessage === defaultSendMessage || dependencies.sendScheduledMessage === defaultSendScheduledMessage)
91
+ && !ensureDefaultInstance(res)) return;
83
92
 
84
93
  try {
85
94
  const payload = {
@@ -136,6 +145,8 @@ const sendBulkMessageController = async (req, res) => {
136
145
  const hasScheduler = typeof dependencies.sendScheduledMessage === 'function';
137
146
  const hasDirectSend = typeof dependencies.sendMessage === 'function';
138
147
  if (!ensureDependency(res, hasScheduler || hasDirectSend, 'No messaging provider configured. Ensure Nexus.initialize() completed before using the message controllers.')) return;
148
+ if ((dependencies.sendMessage === defaultSendMessage || dependencies.sendScheduledMessage === defaultSendScheduledMessage)
149
+ && !ensureDefaultInstance(res)) return;
139
150
 
140
151
  try {
141
152
  let numSend = 0;
@@ -216,6 +227,8 @@ const sendBulkMessageAirtableController = async (req, res) => {
216
227
  const hasScheduler = typeof dependencies.sendScheduledMessage === 'function';
217
228
  const hasDirectSend = typeof dependencies.sendMessage === 'function';
218
229
  if (!ensureDependency(res, hasScheduler || hasDirectSend, 'No messaging provider configured. Ensure Nexus.initialize() completed before using the message controllers.')) return;
230
+ if ((dependencies.sendMessage === defaultSendMessage || dependencies.sendScheduledMessage === defaultSendScheduledMessage)
231
+ && !ensureDefaultInstance(res)) return;
219
232
 
220
233
  const airtableFetcher = dependencies.getRecordByFilter;
221
234
  if (!ensureDependency(res, typeof airtableFetcher === 'function', 'Airtable getRecordByFilter not configured. Call configureMessageController() to inject it.')) return;
@@ -45,6 +45,7 @@ class NexusMessaging {
45
45
  this.processingLocks = new Map(); // Per-chat locks to prevent parallel processing
46
46
  this.activeRequests = new Map(); // Track active AI requests per chat
47
47
  this.abandonedRuns = new Set(); // Track runs that should be ignored
48
+ this.typingIntervals = new Map(); // Track active typing intervals per chat
48
49
  this.batchingConfig = {
49
50
  enabled: config.messageBatching?.enabled ?? true,
50
51
  abortOnNewMessage: config.messageBatching?.abortOnNewMessage ?? true,
@@ -667,8 +668,6 @@ class NexusMessaging {
667
668
  * Handle message with check-after strategy - process immediately, check for new messages after
668
669
  */
669
670
  async _handleWithCheckAfter(chatId) {
670
- const typingInterval = await this._startTypingRefresh(chatId);
671
-
672
671
  if (this.processingLocks.has(chatId)) {
673
672
  logger.info(`[CheckAfter] Already processing ${chatId}, new message will be included`);
674
673
 
@@ -680,6 +679,11 @@ class NexusMessaging {
680
679
  if (this.batchingConfig.immediateRestart) {
681
680
  this.processingLocks.delete(chatId);
682
681
  this.activeRequests.delete(chatId);
682
+ const existingTypingInterval = this.typingIntervals.get(chatId);
683
+ if (existingTypingInterval) {
684
+ clearInterval(existingTypingInterval);
685
+ this.typingIntervals.delete(chatId);
686
+ }
683
687
 
684
688
  logger.info(`[CheckAfter] Starting immediate reprocessing for ${chatId}`);
685
689
  await this._processWithLock(chatId, null);
@@ -688,6 +692,7 @@ class NexusMessaging {
688
692
  return;
689
693
  }
690
694
 
695
+ const typingInterval = await this._startTypingRefresh(chatId);
691
696
  await this._processWithLock(chatId, typingInterval);
692
697
  }
693
698
 
@@ -697,13 +702,20 @@ class NexusMessaging {
697
702
  async _processWithLock(chatId, existingTypingInterval = null) {
698
703
  this.processingLocks.set(chatId, true);
699
704
  let typingInterval = existingTypingInterval;
705
+ if (typingInterval) {
706
+ this.typingIntervals.set(chatId, typingInterval);
707
+ }
700
708
 
701
709
  const runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
702
710
  this.activeRequests.set(chatId, runId);
703
711
 
704
712
  try {
705
713
  if (!typingInterval) {
714
+ // Keep typing intervals scoped to the active processing run to avoid leaks on restarts.
706
715
  typingInterval = await this._startTypingRefresh(chatId);
716
+ if (typingInterval) {
717
+ this.typingIntervals.set(chatId, typingInterval);
718
+ }
707
719
  }
708
720
 
709
721
  const startTime = Date.now();
@@ -755,6 +767,7 @@ class NexusMessaging {
755
767
  if (typingInterval) {
756
768
  clearInterval(typingInterval);
757
769
  typingInterval = null;
770
+ this.typingIntervals.delete(chatId);
758
771
  await new Promise(resolve => setTimeout(resolve, 100));
759
772
  }
760
773
 
@@ -775,7 +788,10 @@ class NexusMessaging {
775
788
  } catch (error) {
776
789
  logger.error('[CheckAfter] Error processing messages:', { chatId, error: error.message });
777
790
  } finally {
778
- if (typingInterval) clearInterval(typingInterval);
791
+ if (typingInterval) {
792
+ clearInterval(typingInterval);
793
+ }
794
+ this.typingIntervals.delete(chatId);
779
795
  if (this.activeRequests.get(chatId) === runId) {
780
796
  this.processingLocks.delete(chatId);
781
797
  this.activeRequests.delete(chatId);
@@ -886,12 +902,19 @@ const setDefaultInstance = (instance) => {
886
902
 
887
903
  const getDefaultInstance = () => defaultInstance;
888
904
 
905
+ const requireDefaultInstance = () => {
906
+ if (!defaultInstance) {
907
+ throw new Error('NexusMessaging default instance not initialized');
908
+ }
909
+ return defaultInstance;
910
+ };
911
+
889
912
  const sendMessage = async (messageData) => {
890
- return await defaultInstance.sendMessage(messageData);
913
+ return await requireDefaultInstance().sendMessage(messageData);
891
914
  };
892
915
 
893
916
  const sendScheduledMessage = async (scheduledMessage) => {
894
- return await defaultInstance.sendScheduledMessage(scheduledMessage);
917
+ return await requireDefaultInstance().sendScheduledMessage(scheduledMessage);
895
918
  };
896
919
 
897
920
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",