@peopl-health/nexus 1.6.1 → 1.6.3
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.
- package/MIGRATION_GUIDE.md +1 -1
- package/README.md +2 -2
- package/examples/consumer-server.js +11 -11
- package/lib/adapters/BaileysProvider.js +6 -6
- package/lib/adapters/TwilioProvider.js +13 -13
- package/lib/core/MessageProvider.js +1 -1
- package/lib/core/NexusMessaging.js +4 -10
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -2
- package/lib/interactive/index.js +3 -3
- package/lib/storage/MongoStorage.js +1 -1
- package/package.json +1 -1
package/MIGRATION_GUIDE.md
CHANGED
|
@@ -20,7 +20,7 @@ await nexus.sendMessage({ code: 'whatsapp:+521555...', message: 'Hi' });
|
|
|
20
20
|
```
|
|
21
21
|
After (preferred):
|
|
22
22
|
```js
|
|
23
|
-
await nexus.sendMessage({
|
|
23
|
+
await nexus.sendMessage({ code: '+521555...', message: 'Hi' });
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Templates & Flows (Twilio)
|
package/README.md
CHANGED
|
@@ -88,10 +88,10 @@ registerFlow('greeting_qr', {
|
|
|
88
88
|
type: 'quick-reply', language: 'es', body: 'Hola {{1}}', variables: { '1': 'Nombre' },
|
|
89
89
|
buttons: [{ text: 'Sí' }, { text: 'No' }]
|
|
90
90
|
});
|
|
91
|
-
await sendInteractive(nexus, {
|
|
91
|
+
await sendInteractive(nexus, { code: '+521555...', id: 'greeting_qr' });
|
|
92
92
|
|
|
93
93
|
registerInteractiveHandler({ type: 'button', id: /sí|si/i }, async (msg, messaging) => {
|
|
94
|
-
await messaging.sendMessage({
|
|
94
|
+
await messaging.sendMessage({ code: msg.from, message: '¡Confirmado!' });
|
|
95
95
|
});
|
|
96
96
|
attachInteractiveRouter(nexus);
|
|
97
97
|
```
|
|
@@ -51,7 +51,7 @@ async function initializeNexus() {
|
|
|
51
51
|
const response = await processTextMessage(messageData);
|
|
52
52
|
if (response) {
|
|
53
53
|
await nexus.sendMessage({
|
|
54
|
-
|
|
54
|
+
code: messageData.from,
|
|
55
55
|
message: response
|
|
56
56
|
});
|
|
57
57
|
}
|
|
@@ -64,7 +64,7 @@ async function initializeNexus() {
|
|
|
64
64
|
switch (command) {
|
|
65
65
|
case 'help':
|
|
66
66
|
await nexus.sendMessage({
|
|
67
|
-
|
|
67
|
+
code: messageData.from,
|
|
68
68
|
message: `🤖 Available Commands:
|
|
69
69
|
/help - Show this help
|
|
70
70
|
/support - Connect to support assistant
|
|
@@ -78,7 +78,7 @@ async function initializeNexus() {
|
|
|
78
78
|
const supportResponse = await assistants.support.handleMessage(messageData.from, 'Hello, I need support.');
|
|
79
79
|
if (supportResponse) {
|
|
80
80
|
await nexus.sendMessage({
|
|
81
|
-
|
|
81
|
+
code: messageData.from,
|
|
82
82
|
message: supportResponse
|
|
83
83
|
});
|
|
84
84
|
}
|
|
@@ -88,7 +88,7 @@ async function initializeNexus() {
|
|
|
88
88
|
const salesResponse = await assistants.sales.handleMessage(messageData.from, 'Hello, I\'m interested in your products.');
|
|
89
89
|
if (salesResponse) {
|
|
90
90
|
await nexus.sendMessage({
|
|
91
|
-
|
|
91
|
+
code: messageData.from,
|
|
92
92
|
message: salesResponse
|
|
93
93
|
});
|
|
94
94
|
}
|
|
@@ -96,7 +96,7 @@ async function initializeNexus() {
|
|
|
96
96
|
|
|
97
97
|
case 'status':
|
|
98
98
|
await nexus.sendMessage({
|
|
99
|
-
|
|
99
|
+
code: messageData.from,
|
|
100
100
|
message: `System Status: ${nexus.isConnected() ? '✅ Connected' : '❌ Disconnected'}`
|
|
101
101
|
});
|
|
102
102
|
break;
|
|
@@ -106,14 +106,14 @@ async function initializeNexus() {
|
|
|
106
106
|
await nexus.getStorage().clearThread(messageData.from);
|
|
107
107
|
}
|
|
108
108
|
await nexus.sendMessage({
|
|
109
|
-
|
|
109
|
+
code: messageData.from,
|
|
110
110
|
message: 'Conversation reset! You can start fresh now.'
|
|
111
111
|
});
|
|
112
112
|
break;
|
|
113
113
|
|
|
114
114
|
default:
|
|
115
115
|
await nexus.sendMessage({
|
|
116
|
-
|
|
116
|
+
code: messageData.from,
|
|
117
117
|
message: `Unknown command: /${command}. Type /help for available commands.`
|
|
118
118
|
});
|
|
119
119
|
}
|
|
@@ -136,10 +136,10 @@ app.post('/webhook', async (req, res) => {
|
|
|
136
136
|
// API endpoints
|
|
137
137
|
app.post('/api/send-message', async (req, res) => {
|
|
138
138
|
try {
|
|
139
|
-
const {
|
|
139
|
+
const { code, message, fileUrl, fileType } = req.body;
|
|
140
140
|
|
|
141
141
|
const result = await nexus.sendMessage({
|
|
142
|
-
|
|
142
|
+
code,
|
|
143
143
|
message,
|
|
144
144
|
fileUrl,
|
|
145
145
|
fileType
|
|
@@ -153,10 +153,10 @@ app.post('/api/send-message', async (req, res) => {
|
|
|
153
153
|
|
|
154
154
|
app.post('/api/send-template', async (req, res) => {
|
|
155
155
|
try {
|
|
156
|
-
const {
|
|
156
|
+
const { code, contentSid, variables } = req.body;
|
|
157
157
|
|
|
158
158
|
const result = await nexus.sendMessage({
|
|
159
|
-
|
|
159
|
+
code,
|
|
160
160
|
contentSid,
|
|
161
161
|
variables
|
|
162
162
|
});
|
|
@@ -84,9 +84,9 @@ class BaileysProvider extends MessageProvider {
|
|
|
84
84
|
throw new Error('Baileys provider not connected');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const {
|
|
87
|
+
const { code, message, fileUrl, fileType, hidePreview = false } = messageData;
|
|
88
88
|
|
|
89
|
-
if (!
|
|
89
|
+
if (!code) {
|
|
90
90
|
throw new Error('Recipient is required');
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -94,7 +94,7 @@ class BaileysProvider extends MessageProvider {
|
|
|
94
94
|
throw new Error('Message or file URL is required');
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const formattedCode = this.formatCode(code);
|
|
98
98
|
let sendOptions = {};
|
|
99
99
|
let formattedMessage = this.formatMessage(message || '');
|
|
100
100
|
|
|
@@ -135,7 +135,7 @@ class BaileysProvider extends MessageProvider {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
try {
|
|
138
|
-
const result = await this.waSocket.sendMessage(
|
|
138
|
+
const result = await this.waSocket.sendMessage(formattedCode, sendOptions);
|
|
139
139
|
return {
|
|
140
140
|
success: true,
|
|
141
141
|
messageId: result?.key?.id,
|
|
@@ -156,7 +156,7 @@ class BaileysProvider extends MessageProvider {
|
|
|
156
156
|
: async (payload) => await this.sendMessage(payload);
|
|
157
157
|
|
|
158
158
|
console.log('[BaileysProvider] Scheduled message created', {
|
|
159
|
-
|
|
159
|
+
code: scheduledMessage.code,
|
|
160
160
|
delay,
|
|
161
161
|
hasMedia: Boolean(scheduledMessage.fileUrl)
|
|
162
162
|
});
|
|
@@ -166,7 +166,7 @@ class BaileysProvider extends MessageProvider {
|
|
|
166
166
|
const payload = { ...scheduledMessage };
|
|
167
167
|
delete payload.__nexusSend;
|
|
168
168
|
console.log('[BaileysProvider] Timer fired', {
|
|
169
|
-
|
|
169
|
+
code: payload.to || payload.code,
|
|
170
170
|
hasMessage: Boolean(payload.message || payload.body),
|
|
171
171
|
hasMedia: Boolean(payload.fileUrl)
|
|
172
172
|
});
|
|
@@ -47,16 +47,16 @@ class TwilioProvider extends MessageProvider {
|
|
|
47
47
|
const { code, message, fileUrl, fileType, variables, contentSid } = messageData;
|
|
48
48
|
|
|
49
49
|
const formattedFrom = this.ensureWhatsAppFormat(this.whatsappNumber);
|
|
50
|
-
const
|
|
50
|
+
const formattedCode = this.ensureWhatsAppFormat(code);
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
if (!formattedFrom || !
|
|
53
|
+
if (!formattedFrom || !formattedCode) {
|
|
54
54
|
throw new Error('Invalid sender or recipient number');
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const messageParams = {
|
|
58
58
|
from: formattedFrom,
|
|
59
|
-
to:
|
|
59
|
+
to: formattedCode
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
// Handle template messages
|
|
@@ -83,7 +83,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
83
83
|
|
|
84
84
|
// Handle media messages
|
|
85
85
|
if (fileUrl && fileType !== 'text') {
|
|
86
|
-
const mediaPrep = await this.prepareOutboundMedia(messageData,
|
|
86
|
+
const mediaPrep = await this.prepareOutboundMedia(messageData, formattedCode);
|
|
87
87
|
const outboundMediaUrl = mediaPrep.mediaUrl || fileUrl;
|
|
88
88
|
messageParams.mediaUrl = [outboundMediaUrl];
|
|
89
89
|
if (!messageParams.body || messageParams.body.trim() === '') {
|
|
@@ -91,7 +91,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
91
91
|
}
|
|
92
92
|
if (mediaPrep.uploaded) {
|
|
93
93
|
console.log('[TwilioProvider] Outbound media uploaded to S3', {
|
|
94
|
-
|
|
94
|
+
code: formattedCode,
|
|
95
95
|
bucket: mediaPrep.bucketName,
|
|
96
96
|
key: mediaPrep.key
|
|
97
97
|
});
|
|
@@ -108,14 +108,14 @@ class TwilioProvider extends MessageProvider {
|
|
|
108
108
|
if (this.messageStorage && typeof this.messageStorage.saveMessage === 'function') {
|
|
109
109
|
try {
|
|
110
110
|
console.log('[TwilioProvider] Persisting outbound message', {
|
|
111
|
-
|
|
111
|
+
code: formattedCode,
|
|
112
112
|
from: formattedFrom,
|
|
113
113
|
hasMedia: Boolean(messageParams.mediaUrl && messageParams.mediaUrl.length),
|
|
114
114
|
hasTemplate: Boolean(messageParams.contentSid)
|
|
115
115
|
});
|
|
116
116
|
await this.messageStorage.saveMessage({
|
|
117
117
|
...messageData,
|
|
118
|
-
|
|
118
|
+
code: formattedCode,
|
|
119
119
|
from: formattedFrom,
|
|
120
120
|
messageId: result.sid,
|
|
121
121
|
provider: 'twilio',
|
|
@@ -147,7 +147,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
147
147
|
: async (payload) => await this.sendMessage(payload);
|
|
148
148
|
|
|
149
149
|
console.log('[TwilioProvider] Scheduled message created', {
|
|
150
|
-
|
|
150
|
+
code: scheduledMessage.code,
|
|
151
151
|
delay,
|
|
152
152
|
hasContentSid: Boolean(scheduledMessage.contentSid)
|
|
153
153
|
});
|
|
@@ -158,7 +158,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
158
158
|
const payload = scheduledMessage.toObject ? scheduledMessage.toObject() : { ...scheduledMessage };
|
|
159
159
|
delete payload.__nexusSend;
|
|
160
160
|
console.log('[TwilioProvider] Timer fired', {
|
|
161
|
-
|
|
161
|
+
code: payload.code,
|
|
162
162
|
hasMessage: Boolean(payload.message || payload.body),
|
|
163
163
|
hasMedia: Boolean(payload.fileUrl)
|
|
164
164
|
});
|
|
@@ -176,7 +176,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
176
176
|
return true;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
async prepareOutboundMedia(messageData,
|
|
179
|
+
async prepareOutboundMedia(messageData, formattedCode) {
|
|
180
180
|
const bucketName = runtimeConfig.get('AWS_S3_BUCKET_NAME') || process.env.AWS_S3_BUCKET_NAME;
|
|
181
181
|
const fileUrl = messageData.fileUrl;
|
|
182
182
|
|
|
@@ -221,7 +221,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
221
221
|
|
|
222
222
|
if (!validation.valid) {
|
|
223
223
|
console.warn('[TwilioProvider] Outbound media validation warning', {
|
|
224
|
-
|
|
224
|
+
code: formattedCode,
|
|
225
225
|
message: validation.message
|
|
226
226
|
});
|
|
227
227
|
}
|
|
@@ -270,7 +270,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
270
270
|
};
|
|
271
271
|
} catch (error) {
|
|
272
272
|
console.error('[TwilioProvider] Failed to upload outbound media to S3. Using original URL.', {
|
|
273
|
-
|
|
273
|
+
code: formattedCode,
|
|
274
274
|
error: error?.message || error
|
|
275
275
|
});
|
|
276
276
|
|
|
@@ -556,7 +556,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
556
556
|
const content = await this.getTemplate(contentSid);
|
|
557
557
|
try {
|
|
558
558
|
const links = (content && content.links) || {};
|
|
559
|
-
let approvalsUrl = links.approvals || links.approvalRequests || links.approval_requests;
|
|
559
|
+
let approvalsUrl = links.approval_create || links.approvals || links.approvalRequests || links.approval_requests;
|
|
560
560
|
if (!approvalsUrl) {
|
|
561
561
|
return {
|
|
562
562
|
success: false,
|
|
@@ -27,7 +27,7 @@ class MessageProvider {
|
|
|
27
27
|
/**
|
|
28
28
|
* Send a message
|
|
29
29
|
* @param {Object} messageData - Message data
|
|
30
|
-
* @param {string} messageData.
|
|
30
|
+
* @param {string} messageData.code - Recipient
|
|
31
31
|
* @param {string} messageData.message - Message text
|
|
32
32
|
* @param {string} messageData.fileUrl - Optional file URL
|
|
33
33
|
* @param {string} messageData.fileType - File type (text, image, document, audio)
|
|
@@ -270,13 +270,7 @@ class NexusMessaging {
|
|
|
270
270
|
throw new Error('No provider initialized');
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
const normalized = { ...messageData };
|
|
275
|
-
if (!normalized.to && normalized.code) {
|
|
276
|
-
normalized.to = normalized.code;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const result = await this.provider.sendMessage(normalized);
|
|
273
|
+
const result = await this.provider.sendMessage(messageData);
|
|
280
274
|
|
|
281
275
|
// Store message only if provider does not handle persistence itself
|
|
282
276
|
const providerStoresMessage = typeof this.provider.supportsMessageStorage === 'function'
|
|
@@ -285,7 +279,7 @@ class NexusMessaging {
|
|
|
285
279
|
|
|
286
280
|
if (this.messageStorage && !providerStoresMessage) {
|
|
287
281
|
await this.messageStorage.saveMessage({
|
|
288
|
-
...
|
|
282
|
+
...messageData,
|
|
289
283
|
messageId: result.messageId,
|
|
290
284
|
provider: result.provider,
|
|
291
285
|
timestamp: new Date(),
|
|
@@ -405,7 +399,7 @@ class NexusMessaging {
|
|
|
405
399
|
|
|
406
400
|
if (response) {
|
|
407
401
|
await this.sendMessage({
|
|
408
|
-
|
|
402
|
+
code: from,
|
|
409
403
|
message: response
|
|
410
404
|
});
|
|
411
405
|
}
|
|
@@ -455,7 +449,7 @@ class NexusMessaging {
|
|
|
455
449
|
|
|
456
450
|
if (response) {
|
|
457
451
|
await this.sendMessage({
|
|
458
|
-
|
|
452
|
+
code: from,
|
|
459
453
|
message: response
|
|
460
454
|
});
|
|
461
455
|
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -198,7 +198,7 @@ class Nexus {
|
|
|
198
198
|
/**
|
|
199
199
|
* Send a message
|
|
200
200
|
* @param {Object} messageData - Message data
|
|
201
|
-
* @param {string} messageData.
|
|
201
|
+
* @param {string} messageData.code - Recipient phone number
|
|
202
202
|
* @param {string} messageData.message - Message text
|
|
203
203
|
* @param {string} [messageData.fileUrl] - Optional file URL
|
|
204
204
|
* @param {string} [messageData.fileType] - File type
|
|
@@ -216,7 +216,7 @@ class Nexus {
|
|
|
216
216
|
/**
|
|
217
217
|
* Send a scheduled message
|
|
218
218
|
* @param {Object} scheduledMessage - Scheduled message data
|
|
219
|
-
* @param {string} scheduledMessage.
|
|
219
|
+
* @param {string} scheduledMessage.code - Recipient phone number
|
|
220
220
|
* @param {string} scheduledMessage.message - Message text
|
|
221
221
|
* @param {Date|string} scheduledMessage.sendAt - When to send the message
|
|
222
222
|
* @returns {Promise<Object>} Scheduled message result
|
package/lib/interactive/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const { toTwilioContent } = require('./twilioMapper');
|
|
|
3
3
|
const { registerFlow, getFlow, listFlows, registerInteractiveHandler, listInteractiveHandlers } = require('./registry');
|
|
4
4
|
|
|
5
5
|
async function sendInteractive(nexusOrMessaging, params) {
|
|
6
|
-
const {
|
|
6
|
+
const { code, spec, id, variables } = params || {};
|
|
7
7
|
if (!nexusOrMessaging) throw new Error('sendInteractive requires a Nexus or NexusMessaging instance');
|
|
8
8
|
const messaging = typeof nexusOrMessaging.getMessaging === 'function' ? nexusOrMessaging.getMessaging() : nexusOrMessaging;
|
|
9
9
|
const provider = typeof messaging.getProvider === 'function' ? messaging.getProvider() : null;
|
|
@@ -14,14 +14,14 @@ async function sendInteractive(nexusOrMessaging, params) {
|
|
|
14
14
|
|
|
15
15
|
// If user supplied a contentSid directly in spec, just send it
|
|
16
16
|
if (useSpec.contentSid) {
|
|
17
|
-
return await provider.sendMessage({
|
|
17
|
+
return await provider.sendMessage({ code, contentSid: useSpec.contentSid, variables });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Twilio mapping
|
|
21
21
|
if (provider.constructor && provider.constructor.name === 'TwilioProvider') {
|
|
22
22
|
const content = toTwilioContent(useSpec);
|
|
23
23
|
const created = await provider.createTemplate(content);
|
|
24
|
-
return await provider.sendMessage({
|
|
24
|
+
return await provider.sendMessage({ code, contentSid: created.sid, variables });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Baileys or others: not supported yet
|
|
@@ -62,7 +62,7 @@ class MongoStorage {
|
|
|
62
62
|
async saveMessage(messageData) {
|
|
63
63
|
try {
|
|
64
64
|
console.log('[MongoStorage] saveMessage called', {
|
|
65
|
-
|
|
65
|
+
code: messageData?.to || messageData?.code || messageData?.numero,
|
|
66
66
|
from: messageData?.from,
|
|
67
67
|
provider: messageData?.provider || 'unknown',
|
|
68
68
|
hasRaw: Boolean(messageData?.raw),
|