@peopl-health/nexus 1.1.5 → 1.1.7

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.
@@ -15,7 +15,7 @@ class BaileysProvider extends MessageProvider {
15
15
  async initialize() {
16
16
  try {
17
17
  const { default: makeWASocket, useMultiFileAuthState } = require('baileys');
18
- const { useMongoDBAuthState } = require('../utils/mongoAuthConfig');
18
+ const { useMongoDBAuthState } = require('../config/mongoAuthConfig');
19
19
  const pino = require('pino');
20
20
 
21
21
  const logger = pino({ level: 'warn' });
@@ -0,0 +1,103 @@
1
+ const AWS = require('aws-sdk');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ AWS.config.update({
6
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
7
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
8
+ region: 'us-east-1',
9
+ signatureVersion: 'v4'
10
+ });
11
+
12
+ const s3 = new AWS.S3();
13
+
14
+
15
+ async function uploadBufferToS3(buffer, bucketName, key, contentType, isWhatsAppMedia = false) {
16
+ const params = {
17
+ Bucket: bucketName,
18
+ Key: key,
19
+ Body: buffer,
20
+ ContentType: contentType,
21
+ Metadata: isWhatsAppMedia ? {
22
+ 'x-amz-meta-whatsapp': 'true',
23
+ 'x-amz-meta-expiry': new Date(new Date().getTime() + (7 * 24 * 60 * 60 * 1000)).toISOString() // 7 days expiry
24
+ } : {}
25
+ };
26
+
27
+ try {
28
+ const data = await s3.upload(params).promise();
29
+ console.log(`File uploaded successfully at ${data.Location}`);
30
+ return data.Location;
31
+ } catch (error) {
32
+ console.error(`Error uploading file: ${error.message}`);
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ async function downloadFileFromS3(bucketName, key, downloadPath) {
38
+ console.log(`[S3] Attempting to download file from S3 - Bucket: ${bucketName}, Key: ${key}`);
39
+
40
+ const headParams = {
41
+ Bucket: bucketName,
42
+ Key: key
43
+ };
44
+
45
+ try {
46
+ await s3.headObject(headParams).promise();
47
+ console.log(`[S3] Key exists in bucket: ${key}`);
48
+ } catch (headErr) {
49
+ console.error(`[S3] Object check failed - Key: ${key}, Error: ${headErr.code} - ${headErr.message}`);
50
+ if (headErr.code === 'NotFound') {
51
+ console.log(`[S3] Key does not exist in bucket: ${key}`);
52
+ }
53
+ throw headErr;
54
+ }
55
+
56
+ const params = {
57
+ Bucket: bucketName,
58
+ Key: key
59
+ };
60
+
61
+ try {
62
+ console.log(`[S3] Downloading file data - Key: ${key}`);
63
+ const data = await s3.getObject(params).promise();
64
+ console.log(`[S3] Successfully retrieved file - Key: ${key}, ContentType: ${data.ContentType}, Size: ${data.ContentLength} bytes`);
65
+
66
+ if (downloadPath) {
67
+ const directory = path.dirname(downloadPath);
68
+ fs.mkdirSync(directory, { recursive: true });
69
+ fs.writeFileSync(downloadPath, data.Body);
70
+ console.log(`[S3] File saved to disk at: ${downloadPath}`);
71
+ return;
72
+ }
73
+
74
+ return data;
75
+ } catch (error) {
76
+ console.error(`[S3] Error downloading file - Key: ${key}, Error: ${error.code} - ${error.message}`);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ async function generatePresignedUrl(bucketName, key, expiration = 300) {
82
+ const params = {
83
+ Bucket: bucketName,
84
+ Key: key,
85
+ Expires: expiration
86
+ };
87
+
88
+ try {
89
+ const url = s3.getSignedUrlPromise('getObject', params);
90
+ console.log(`Presigned URL generated: ${url}`);
91
+ return url;
92
+ } catch (error) {
93
+ console.error(`Error generating presigned URL: ${error.message}`);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ module.exports = {
99
+ s3,
100
+ uploadBufferToS3,
101
+ downloadFileFromS3,
102
+ generatePresignedUrl
103
+ };
@@ -0,0 +1,4 @@
1
+ // Temporary stub for llmConfig - should be replaced with Nexus LLM provider
2
+ module.exports = {
3
+ openaiClient: null // This will be replaced with Nexus LLM provider
4
+ };
@@ -1,30 +1,11 @@
1
- // Optional config - will be undefined if not available
2
- let Config_ID;
3
- try {
4
- Config_ID = require('../config/airtableConfig')?.Config_ID;
5
- } catch (e) {
6
- Config_ID = null;
7
- }
8
-
9
- // Optional model imports
10
- let updateThreadActive, updateThreadStop, Thread;
11
- try {
12
- const threadModel = require('../models/threadModel');
13
- updateThreadActive = threadModel.updateThreadActive;
14
- updateThreadStop = threadModel.updateThreadStop;
15
- Thread = threadModel.Thread;
16
- } catch (e) {
17
- // Models not available
18
- }
19
-
20
- // Optional service imports - stub functions for missing services
21
- const getRecordByFilter = () => Promise.resolve(null);
22
- const createAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
23
- const addMsgAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
24
- const addInsAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
25
- const getThreadInfo = () => Promise.resolve(null);
26
- const switchAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
27
- const sendMessage = () => Promise.resolve({ success: false, error: 'Service not available' });
1
+ const { Config_ID } = require('../config/airtableConfig');
2
+
3
+ const { updateThreadActive, updateThreadStop, Thread } = require('../models/threadModel');
4
+
5
+ const { getRecordByFilter } = require('../services/airtableService');
6
+ const { createAssistant, addMsgAssistant, addInsAssistant } = require('../services/assistantService');
7
+ const { getThreadInfo, switchAssistant } = require('../services/assistantService');
8
+ const { sendMessage } = require('../core/NexusMessaging');
28
9
 
29
10
 
30
11
  const activeAssistantController = async (req, res) => {
@@ -96,7 +77,7 @@ const createAssistantController = async (req, res) => {
96
77
  };
97
78
 
98
79
  const getInfoAssistantController = async (req, res) => {
99
- const { code } = req.query;
80
+ const { code } = req.body;
100
81
 
101
82
  try {
102
83
  let threadInfo = await getThreadInfo(code);
@@ -1,7 +1,10 @@
1
- const { Message } = require('../models/messageModel');
1
+ // Use mongoose models directly to avoid conflicts
2
+ const mongoose = require('mongoose');
2
3
  const { fetchConversationData, processConversations } = require('../services/conversationService');
3
4
  const { sendMessage } = require('../core/NexusMessaging');
4
5
 
6
+ const Message = mongoose.models.Message;
7
+
5
8
  const getConversationController = async (req, res) => {
6
9
  const startTime = Date.now();
7
10
  console.log('Starting getConversationController at', new Date().toISOString());
@@ -15,7 +18,15 @@ const getConversationController = async (req, res) => {
15
18
 
16
19
  console.log(`Pagination: page ${page}, limit ${limit}, skip ${skip}, filter: ${filter}`);
17
20
 
18
- // Check if messages exist
21
+ if (!Message) {
22
+ console.log('Message model not found, returning empty conversations list');
23
+ return res.status(200).json({
24
+ success: true,
25
+ conversations: [],
26
+ emptyDatabase: true
27
+ });
28
+ }
29
+
19
30
  const messageCount = await Message.countDocuments({});
20
31
  console.log('Total message count:', messageCount);
21
32
 
@@ -1,23 +1,51 @@
1
- // Stub functions for missing services
2
- const TwilioService = {
3
- createTemplate: () => Promise.resolve({ success: false, error: 'Service not available' }),
4
- deleteTemplate: () => Promise.resolve({ success: false, error: 'Service not available' }),
5
- listTemplates: () => Promise.resolve([]),
6
- getTemplate: () => Promise.resolve(null)
1
+ // Nexus provider will be injected - templates only work with Twilio
2
+ let nexusProvider = null;
3
+
4
+ // Configure Nexus provider
5
+ const configureNexusProvider = (provider) => {
6
+ nexusProvider = provider;
7
7
  };
8
- const handleApiError = (res, error, message) => {
9
- console.error(message, error);
10
- return res.status(500).json({ success: false, error: message });
8
+
9
+ // Check if provider supports templates
10
+ const checkTemplateSupport = () => {
11
+ if (!nexusProvider) {
12
+ throw new Error('Nexus provider not configured');
13
+ }
14
+
15
+ // Only Twilio provider supports template operations
16
+ if (!nexusProvider.listTemplates || typeof nexusProvider.listTemplates !== 'function') {
17
+ throw new Error('Template operations are only supported with Twilio provider');
18
+ }
11
19
  };
20
+ const mongoose = require('mongoose');
21
+ const { handleApiError } = require('../utils/errorHandler');
12
22
 
13
- // Stub imports for missing dependencies
14
- const Template = class { constructor() {} };
15
- const TemplateModel = {
16
- create: () => Promise.resolve({ success: false, error: 'Model not available' }),
17
- find: () => Promise.resolve([]),
18
- findById: () => Promise.resolve(null)
23
+ const { Template } = require('../templates/templateStructure');
24
+ const predefinedTemplates = require('../templates/predefinedTemplates');
25
+
26
+ // Use mongoose models to avoid conflicts
27
+ const getTemplateModel = () => {
28
+ if (mongoose.models.Template) {
29
+ return mongoose.models.Template;
30
+ }
31
+
32
+ // If not in mongoose.models, require and return the model
33
+ try {
34
+ const TemplateModel = require('../models/templateModel');
35
+ return TemplateModel;
36
+ } catch (error) {
37
+ console.error('Failed to load Template model:', error);
38
+ // Return a stub model with required methods
39
+ return {
40
+ deleteMany: () => Promise.resolve({ deletedCount: 0 }),
41
+ find: () => ({ sort: () => ({ limit: () => ({ lean: () => Promise.resolve([]) }) }) }),
42
+ findOne: () => Promise.resolve(null),
43
+ create: () => Promise.resolve({}),
44
+ updateOne: () => Promise.resolve({}),
45
+ deleteOne: () => Promise.resolve({})
46
+ };
47
+ }
19
48
  };
20
- const predefinedTemplates = [];
21
49
 
22
50
  /**
23
51
  * Create a new template and store it in both Twilio and our database
@@ -109,6 +137,7 @@ const createTemplate = async (req, res) => {
109
137
  dateCreated = currentDate;
110
138
  }
111
139
 
140
+ const TemplateModel = getTemplateModel();
112
141
  const newDbTemplate = await TemplateModel.create({
113
142
  sid: twilioContent.sid,
114
143
  name: (twilioContent.friendlyName || `template_${twilioContent.sid}`).replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase(),
@@ -152,8 +181,13 @@ const createTemplate = async (req, res) => {
152
181
  const listTemplates = async (req, res) => {
153
182
  try {
154
183
  const { status: queryStatus, type, limit = 50, showFlows: queryShowFlows } = req.query;
184
+ const TemplateModel = getTemplateModel();
155
185
 
156
- const twilioRawTemplates = await TwilioService.listTemplates({ limit: parseInt(limit, 10) });
186
+ console.log('nexusProvider:', nexusProvider ? 'configured' : 'not configured');
187
+ console.log('nexusProvider methods:', nexusProvider ? Object.keys(nexusProvider) : 'none');
188
+
189
+ checkTemplateSupport();
190
+ const twilioRawTemplates = await nexusProvider.listTemplates({ limit: parseInt(limit, 10) });
157
191
 
158
192
  const showFlows = type === 'flow' || queryShowFlows === 'true';
159
193
  const filteredTwilioTemplates = twilioRawTemplates.filter(template => {
@@ -176,7 +210,7 @@ const listTemplates = async (req, res) => {
176
210
  };
177
211
 
178
212
  try {
179
- const approvalInfo = await TwilioService.checkApprovalStatus(twilioTemplate.sid);
213
+ const approvalInfo = await nexusProvider.checkApprovalStatus(twilioTemplate.sid);
180
214
  if (approvalInfo && approvalInfo.approvalRequest) {
181
215
  const reqData = approvalInfo.approvalRequest;
182
216
  updateFields.approvalRequest = {
@@ -260,10 +294,12 @@ const getTemplate = async (req, res) => {
260
294
  try {
261
295
  const { id } = req.params;
262
296
 
297
+ const TemplateModel = getTemplateModel();
263
298
  let template = await TemplateModel.findOne({ sid: id });
264
299
  let twilioTemplate;
265
300
 
266
- twilioTemplate = await TwilioService.getTemplate(id);
301
+ checkTemplateSupport();
302
+ twilioTemplate = await nexusProvider.getTemplate(id);
267
303
 
268
304
  if (!template) {
269
305
  // If template wasn't in our database, create it based on Twilio data
@@ -345,7 +381,8 @@ const submitForApproval = async (req, res) => {
345
381
  const approvalName = `${name || 'template'}_${timestamp}_${randomSuffix}`.toLowerCase().replace(/[^a-z0-9_]/g, '_');
346
382
  const approvalCategory = category || 'UTILITY';
347
383
 
348
- const response = await TwilioService.submitForApproval(contentSid, approvalName, approvalCategory);
384
+ checkTemplateSupport();
385
+ const response = await nexusProvider.submitForApproval(contentSid, approvalName, approvalCategory);
349
386
 
350
387
  const dateCreated = response.date_created || response.dateCreated;
351
388
  const dateUpdated = response.date_updated || response.dateUpdated || dateCreated;
@@ -356,6 +393,7 @@ const submitForApproval = async (req, res) => {
356
393
  const validSubmittedDate = !isNaN(submittedDate.getTime()) ? submittedDate : new Date();
357
394
  const validUpdatedDate = !isNaN(updatedDate.getTime()) ? updatedDate : new Date();
358
395
 
396
+ const TemplateModel = getTemplateModel();
359
397
  await TemplateModel.updateOne(
360
398
  { sid: contentSid },
361
399
  {
@@ -397,10 +435,12 @@ const checkApprovalStatus = async (req, res) => {
397
435
  return res.status(400).json({ success: false, error: 'Content SID is required' });
398
436
  }
399
437
 
438
+ const TemplateModel = getTemplateModel();
400
439
  const dbTemplate = await TemplateModel.findOne({ sid: contentSid });
401
440
 
402
441
  try {
403
- const status = await TwilioService.checkApprovalStatus(contentSid);
442
+ checkTemplateSupport();
443
+ const status = await nexusProvider.checkApprovalStatus(contentSid);
404
444
 
405
445
  if (dbTemplate) {
406
446
  // Use approval status as the authoritative source if available
@@ -475,8 +515,10 @@ const deleteTemplate = async (req, res) => {
475
515
  try {
476
516
  const { id } = req.params;
477
517
 
478
- await TwilioService.deleteTemplate(id);
518
+ checkTemplateSupport();
519
+ await nexusProvider.deleteTemplate(id);
479
520
 
521
+ const TemplateModel = getTemplateModel();
480
522
  await TemplateModel.deleteOne({ sid: id });
481
523
 
482
524
  return res.json({
@@ -529,6 +571,7 @@ const getCompleteTemplate = async (req, res) => {
529
571
  try {
530
572
  const { sid } = req.params;
531
573
 
574
+ const TemplateModel = getTemplateModel();
532
575
  let template = await TemplateModel.findOne({ sid }).lean();
533
576
 
534
577
  if (!template) {
@@ -540,7 +583,8 @@ const getCompleteTemplate = async (req, res) => {
540
583
 
541
584
  if (!template.body || !template.variables || template.variables.length === 0) {
542
585
  try {
543
- const twilioTemplate = await TwilioService.getTemplate(sid);
586
+ checkTemplateSupport();
587
+ const twilioTemplate = await nexusProvider.getTemplate(sid);
544
588
  console.log('Fetched template from Twilio:', twilioTemplate);
545
589
 
546
590
  let body = '';
@@ -603,6 +647,7 @@ const getCompleteTemplate = async (req, res) => {
603
647
  updateData.actions = template.actions;
604
648
  }
605
649
 
650
+ const TemplateModel = getTemplateModel();
606
651
  await TemplateModel.updateOne(
607
652
  { sid },
608
653
  { $set: updateData }
@@ -628,15 +673,6 @@ const getCompleteTemplate = async (req, res) => {
628
673
  }
629
674
  };
630
675
 
631
- // Flow functions (inline since templateFlowController was removed)
632
- const createFlow = async (req, res) => {
633
- return res.status(501).json({ success: false, error: 'Flow creation not available' });
634
- };
635
-
636
- const deleteFlow = async (req, res) => {
637
- return res.status(501).json({ success: false, error: 'Flow deletion not available' });
638
- };
639
-
640
676
  module.exports = {
641
677
  createTemplate,
642
678
  listTemplates,
@@ -646,6 +682,5 @@ module.exports = {
646
682
  checkApprovalStatus,
647
683
  deleteTemplate,
648
684
  getCompleteTemplate,
649
- createFlow,
650
- deleteFlow
685
+ configureNexusProvider
651
686
  };
@@ -0,0 +1,108 @@
1
+ // Nexus provider will be injected
2
+ let nexusProvider = null;
3
+
4
+ // Configure Nexus provider
5
+ const configureNexusProvider = (provider) => {
6
+ nexusProvider = provider;
7
+ };
8
+
9
+ // Check if provider supports templates
10
+ const checkTemplateSupport = () => {
11
+ if (!nexusProvider) {
12
+ throw new Error('Nexus provider not configured. Call configureNexusProvider() first.');
13
+ }
14
+
15
+ if (!nexusProvider.createTemplate || typeof nexusProvider.createTemplate !== 'function') {
16
+ throw new Error('Template operations are only supported with Twilio provider');
17
+ }
18
+ };
19
+ const { handleApiError } = require('../utils/errorHandler');
20
+
21
+
22
+ const createFlow = async (req, res) => {
23
+ try {
24
+ const { friendlyName, language, flowType, body, buttonText, subtitle, pages } = req.body;
25
+
26
+ const missingParams = [];
27
+ if (!friendlyName) missingParams.push('friendlyName');
28
+ if (!language) missingParams.push('language');
29
+ if (!pages || !Array.isArray(pages)) missingParams.push('pages');
30
+
31
+ if (missingParams.length > 0) {
32
+ return res.status(400).json({
33
+ success: false,
34
+ error: `Missing required parameters: ${missingParams.join(', ')}`
35
+ });
36
+ }
37
+
38
+ // Generate unique identifiers for anti-rejection
39
+ const timestamp = Date.now().toString();
40
+ const uniqueId = require('uuid').v4().substring(0, 6);
41
+ const uniqueFriendlyName = `${friendlyName}_${timestamp}_${uniqueId}`;
42
+
43
+ // Match Twilio's expected structure for flex templates
44
+ checkTemplateSupport();
45
+ const content = await nexusProvider.createTemplate({
46
+ friendly_name: uniqueFriendlyName,
47
+ language,
48
+ categories: ['UTILITY'],
49
+ types: {
50
+ 'twilio/flows': {
51
+ body: body,
52
+ button_text: buttonText,
53
+ subtitle: subtitle || null,
54
+ pages: pages,
55
+ type: flowType
56
+ }
57
+ }
58
+ });
59
+
60
+ return res.status(201).json({
61
+ success: true,
62
+ message: 'Flow template created successfully',
63
+ contentSid: content.sid,
64
+ friendlyName: content.friendly_name,
65
+ approvalStatus: 'Pending review',
66
+ approvalLinks: content.links
67
+ });
68
+ } catch (error) {
69
+ return handleApiError(res, error, 'Error creating WhatsApp flow template');
70
+ }
71
+ };
72
+
73
+
74
+ const deleteFlow = async (req, res) => {
75
+ try {
76
+ const { sid: contentSid } = req.params;
77
+
78
+ if (!contentSid) {
79
+ return res.status(400).json({
80
+ success: false,
81
+ error: 'Content SID is required'
82
+ });
83
+ }
84
+
85
+ checkTemplateSupport();
86
+ await nexusProvider.deleteTemplate(contentSid);
87
+
88
+ return res.status(200).json({
89
+ success: true,
90
+ message: 'Template deleted successfully'
91
+ });
92
+ } catch (error) {
93
+ if (error.code === 20404) {
94
+ return res.status(404).json({
95
+ success: false,
96
+ error: 'Template not found'
97
+ });
98
+ }
99
+
100
+ return handleApiError(res, error, 'Error deleting template');
101
+ }
102
+ };
103
+
104
+ module.exports = {
105
+ createFlow,
106
+ deleteFlow,
107
+ configureNexusProvider
108
+ };
@@ -0,0 +1,82 @@
1
+ const multer = require('multer');
2
+ const { v4: uuidv4 } = require('uuid');
3
+ const { generatePresignedUrl, uploadBufferToS3 } = require('../config/awsConfig');
4
+ const bucketName = process.env.AWS_S3_BUCKET_NAME;
5
+
6
+ const storage = multer.memoryStorage();
7
+
8
+ const fileFilter = (req, file, cb) => {
9
+ if (file.mimetype.startsWith('image/') ||
10
+ file.mimetype.startsWith('application/') ||
11
+ file.mimetype.startsWith('audio/') ||
12
+ file.mimetype.startsWith('video/')) {
13
+ cb(null, true);
14
+ } else {
15
+ cb(new Error('Unsupported file type'), false);
16
+ }
17
+ };
18
+
19
+ const upload = multer({
20
+ storage: storage,
21
+ fileFilter: fileFilter,
22
+ limits: {
23
+ fileSize: 50 * 1024 * 1024, // Increased to 50MB
24
+ }
25
+ });
26
+
27
+ const handleFileUpload = async (req, res) => {
28
+ try {
29
+ if (!req.file) {
30
+ return res.status(400).json({
31
+ success: false,
32
+ error: 'No file provided'
33
+ });
34
+ }
35
+
36
+ const file = req.file;
37
+
38
+ let filePrefix = 'document';
39
+ if (file.mimetype.startsWith('image/')) {
40
+ filePrefix = 'image';
41
+ } else if (file.mimetype.startsWith('video/')) {
42
+ filePrefix = 'video';
43
+ } else if (file.mimetype.startsWith('audio/')) {
44
+ filePrefix = 'audio';
45
+ }
46
+
47
+ const uniqueId = uuidv4();
48
+ const originalFileName = file.originalname;
49
+ const sanitizedFileName = file.originalname.replace(/[^a-zA-Z0-9.]/g, '_').replace('.pdf', '');
50
+
51
+ const key = `${filePrefix}/${sanitizedFileName}_${uniqueId}`;
52
+
53
+ await uploadBufferToS3(file.buffer, bucketName, key, file.mimetype, true);
54
+ const whatsappExpirationSeconds = 7 * 24 * 60 * 60;
55
+ const presignedUrl = await generatePresignedUrl(bucketName, key, whatsappExpirationSeconds);
56
+
57
+ res.status(200).json({
58
+ success: true,
59
+ message: 'File uploaded successfully',
60
+ fileUrl: presignedUrl,
61
+ key: key,
62
+ fileType: filePrefix,
63
+ fileName: originalFileName,
64
+ sanitizedFileName: sanitizedFileName,
65
+ fileSize: file.size,
66
+ contentType: file.mimetype
67
+ });
68
+ } catch (error) {
69
+ console.error('Error uploading file:', error);
70
+ res.status(500).json({
71
+ success: false,
72
+ error: error.message || 'Failed to upload file'
73
+ });
74
+ }
75
+ };
76
+
77
+ const uploadSingleFile = upload.single('file');
78
+
79
+ module.exports = {
80
+ uploadSingleFile,
81
+ handleFileUpload
82
+ };
@@ -325,4 +325,18 @@ class NexusMessaging {
325
325
  }
326
326
  }
327
327
 
328
- module.exports = { NexusMessaging };
328
+ const defaultInstance = new NexusMessaging();
329
+
330
+ const sendMessage = async (messageData) => {
331
+ return await defaultInstance.sendMessage(messageData);
332
+ };
333
+
334
+ const sendScheduledMessage = async (scheduledMessage) => {
335
+ return await defaultInstance.sendScheduledMessage(scheduledMessage);
336
+ };
337
+
338
+ module.exports = {
339
+ NexusMessaging,
340
+ sendMessage,
341
+ sendScheduledMessage
342
+ };