@peopl-health/nexus 4.0.2 → 4.1.0

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.
@@ -116,54 +116,24 @@ class TwilioProvider extends MessageProvider {
116
116
  throw new Error('Message must have body, media URL, or content SID');
117
117
  }
118
118
 
119
- const saveMessage = async (body, result) => {
120
- if (messageData._skipStorage) return;
121
- if (!this.messageStorage?.saveMessage) return;
122
- try {
123
- await this.messageStorage.saveMessage({
124
- ...messageData,
125
- body,
126
- code: formattedCode,
127
- from: formattedFrom,
128
- messageId: result.sid,
129
- provider: 'twilio',
130
- timestamp: new Date(),
131
- fromMe: true,
132
- processed: messageData.processed ?? false,
133
- statusInfo: {
134
- status: result.status?.toLowerCase() || null,
135
- updatedAt: new Date()
136
- }
137
- });
138
- } catch (err) {
139
- logger.error('[TwilioProvider] Storage failed:', err);
140
- }
141
- };
142
-
143
- let result;
144
119
  const kind = messageParams.contentSid ? 'template' : 'freeform';
145
120
  const chunks = messageParams.body?.length > 1600 && !messageParams.mediaUrl && !messageParams.contentSid
146
121
  ? this._splitMessageAtWordBoundaries(messageParams.body) : null;
122
+ const sends = chunks ? chunks.map(body => ({ ...messageParams, body })) : [messageParams];
123
+
124
+ let pending = null;
125
+ if (!messageData._skipStorage && this.messageStorage?.savePendingMessage) {
126
+ pending = await this.messageStorage.savePendingMessage({
127
+ ...messageData, code: formattedCode, from: formattedFrom,
128
+ provider: 'twilio', timestamp: new Date(), fromMe: true,
129
+ processed: messageData.processed ?? false
130
+ });
131
+ }
147
132
 
148
- if (chunks) {
149
- for (let i = 0; i < chunks.length; i++) {
150
- result = await this.twilioClient.messages.create({ ...messageParams, body: chunks[i] });
151
- await saveMessage(chunks[i], result);
152
- await recordDeliveryAttempt({ messageData, twilioResult: result, kind });
153
- if (i < chunks.length - 1) await new Promise(r => setTimeout(r, 100));
154
- }
155
- } else {
156
- let pending = null;
157
- if (!messageData._skipStorage && this.messageStorage?.savePendingMessage) {
158
- pending = await this.messageStorage.savePendingMessage({
159
- ...messageData, code: formattedCode, from: formattedFrom,
160
- provider: 'twilio', timestamp: new Date(), fromMe: true,
161
- processed: messageData.processed ?? false
162
- });
163
- }
164
-
133
+ let result;
134
+ for (let i = 0; i < sends.length; i++) {
165
135
  try {
166
- result = await this.twilioClient.messages.create(messageParams);
136
+ result = await this.twilioClient.messages.create(sends[i]);
167
137
  } catch (twilioErr) {
168
138
  await recordDeliveryAttempt({
169
139
  messageData, messageId: pending?._id, kind,
@@ -172,14 +142,15 @@ class TwilioProvider extends MessageProvider {
172
142
  });
173
143
  throw twilioErr;
174
144
  }
175
-
176
- if (pending) {
177
- await this.messageStorage.finalizePendingMessage(pending._id, result.sid, {
178
- status: result.status?.toLowerCase() || null,
179
- updatedAt: new Date()
180
- });
181
- }
182
145
  await recordDeliveryAttempt({ messageData, messageId: pending?._id, twilioResult: result, kind });
146
+ if (i < sends.length - 1) await new Promise(r => setTimeout(r, 100));
147
+ }
148
+
149
+ if (pending) {
150
+ await this.messageStorage.finalizePendingMessage(pending._id, chunks ? null : result.sid, {
151
+ status: result.status?.toLowerCase() || null,
152
+ updatedAt: new Date()
153
+ });
183
154
  }
184
155
 
185
156
  return {
@@ -4,6 +4,7 @@ const { logger } = require('../utils/logger');
4
4
  const { createEvent, safeEmit } = require('../utils/eventUtils');
5
5
 
6
6
  const { Message } = require('../models/messageModel');
7
+ const { DeliveryAttempt } = require('../models/deliveryAttemptModel');
7
8
 
8
9
  const { handle24HourWindowError } = require('../helpers/templateRecoveryHelper');
9
10
  const { updateDeliveryAttemptByTwilioSid } = require('../helpers/deliveryAttemptHelper');
@@ -20,7 +21,7 @@ async function updateMessageStatus(messageSid, status, errorCode = null, errorMe
20
21
  ...(errorMessage && { 'statusInfo.errorMessage': errorMessage })
21
22
  };
22
23
 
23
- const updated = await Message.findOneAndUpdate(
24
+ let updated = await Message.findOneAndUpdate(
24
25
  { $or: [
25
26
  { message_id: messageSid, 'statusInfo.recoveryMessageId': { $exists: false } },
26
27
  { 'statusInfo.recoveryMessageId': messageSid }
@@ -29,6 +30,17 @@ async function updateMessageStatus(messageSid, status, errorCode = null, errorMe
29
30
  { new: true }
30
31
  );
31
32
 
33
+ if (!updated) {
34
+ const attempt = await DeliveryAttempt.findOne({ twilioSid: messageSid }, '_id messageId').lean();
35
+ if (attempt?.messageId) {
36
+ updated = await Message.findOneAndUpdate(
37
+ { _id: attempt.messageId },
38
+ { $set: updateData },
39
+ { new: true }
40
+ );
41
+ }
42
+ }
43
+
32
44
  if (!updated) {
33
45
  logger.warn('[MessageStatus] Message not found', { messageSid });
34
46
  }
@@ -146,7 +146,7 @@ async function insertMessage(values) {
146
146
  isNew = true;
147
147
  } else {
148
148
  const result = await Message.findOneAndUpdate(
149
- { message_id: values.message_id, body: values.body },
149
+ { message_id: values.message_id },
150
150
  { $setOnInsert: messageData },
151
151
  { upsert: true, new: true, setDefaultsOnInsert: true, includeResultMetadata: true }
152
152
  );
@@ -55,10 +55,10 @@ class MongoStorage {
55
55
  }
56
56
 
57
57
  async finalizePendingMessage(docId, sid, statusInfo = null) {
58
- await Message.updateOne(
59
- { _id: docId },
60
- { $set: { message_id: sid, ...(statusInfo && { statusInfo }) } }
61
- );
58
+ const update = { ...(sid && { message_id: sid }), ...(statusInfo && { statusInfo }) };
59
+ if (Object.keys(update).length) {
60
+ await Message.updateOne({ _id: docId }, { $set: update });
61
+ }
62
62
  safeEmit(getStatusEventBus(), 'message:status', createEvent('message:status', {
63
63
  messageId: String(docId),
64
64
  status: statusInfo?.status || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",