@peopl-health/nexus 4.0.2 → 4.1.1
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/lib/adapters/TwilioProvider.js +22 -51
- package/lib/helpers/deliveryAttemptHelper.js +2 -1
- package/lib/helpers/messageStatusHelper.js +13 -1
- package/lib/jobs/TemplateApprovalJob.js +2 -1
- package/lib/models/deliveryAttemptModel.js +1 -0
- package/lib/models/messageModel.js +1 -1
- package/lib/storage/MongoStorage.js +4 -4
- package/package.json +1 -1
|
@@ -116,70 +116,41 @@ 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
|
-
|
|
149
|
-
|
|
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(
|
|
136
|
+
result = await this.twilioClient.messages.create(sends[i]);
|
|
167
137
|
} catch (twilioErr) {
|
|
168
138
|
await recordDeliveryAttempt({
|
|
169
|
-
messageData, messageId: pending?._id, kind,
|
|
139
|
+
messageData, messageId: pending?._id, kind, body: sends[i].body || null,
|
|
170
140
|
errorSource: 'twilio_sync',
|
|
171
141
|
errorCode: twilioErr.code, errorMessage: twilioErr.message
|
|
172
142
|
});
|
|
173
143
|
throw twilioErr;
|
|
174
144
|
}
|
|
145
|
+
await recordDeliveryAttempt({ messageData, messageId: pending?._id, twilioResult: result, kind, body: sends[i].body || null });
|
|
146
|
+
if (i < sends.length - 1) await new Promise(r => setTimeout(r, 100));
|
|
147
|
+
}
|
|
175
148
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
await recordDeliveryAttempt({ messageData, messageId: pending?._id, twilioResult: result, kind });
|
|
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,7 +4,7 @@ const { Message } = require('../models/messageModel');
|
|
|
4
4
|
const { DeliveryAttempt } = require('../models/deliveryAttemptModel');
|
|
5
5
|
|
|
6
6
|
async function recordDeliveryAttempt({
|
|
7
|
-
messageData = null, messageId = null, twilioResult = null, kind,
|
|
7
|
+
messageData = null, messageId = null, twilioResult = null, kind, body = null,
|
|
8
8
|
errorSource = null, errorCode = null, errorMessage = null
|
|
9
9
|
}) {
|
|
10
10
|
const sid = twilioResult?.sid || null;
|
|
@@ -30,6 +30,7 @@ async function recordDeliveryAttempt({
|
|
|
30
30
|
kind,
|
|
31
31
|
twilioSid: sid,
|
|
32
32
|
contentSid: messageData?.contentSid || null,
|
|
33
|
+
body,
|
|
33
34
|
status,
|
|
34
35
|
errorSource,
|
|
35
36
|
errorCode,
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -148,7 +148,8 @@ class TemplateApprovalJob extends BaseJob {
|
|
|
148
148
|
messageData: { contentSid: templateSid },
|
|
149
149
|
messageId: message._id,
|
|
150
150
|
twilioResult: { sid: recoveryMessageId, status: sendResult?.status },
|
|
151
|
-
kind: 'recovery_template'
|
|
151
|
+
kind: 'recovery_template',
|
|
152
|
+
body: message.body
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
provider.deleteTemplate(templateSid).catch((deleteErr) =>
|
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
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,
|