@peopl-health/nexus 4.1.2 → 4.1.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.
|
@@ -121,13 +121,14 @@ class TwilioProvider extends MessageProvider {
|
|
|
121
121
|
? this._splitMessageAtWordBoundaries(messageParams.body) : null;
|
|
122
122
|
const sends = chunks ? chunks.map(body => ({ ...messageParams, body })) : [messageParams];
|
|
123
123
|
|
|
124
|
-
let
|
|
125
|
-
if (!messageData._skipStorage && this.messageStorage?.savePendingMessage) {
|
|
126
|
-
pending = await this.messageStorage.savePendingMessage({
|
|
124
|
+
let parentId = messageData.parentMessageId || null;
|
|
125
|
+
if (!parentId && !messageData._skipStorage && this.messageStorage?.savePendingMessage) {
|
|
126
|
+
const pending = await this.messageStorage.savePendingMessage({
|
|
127
127
|
...messageData, code: formattedCode, from: formattedFrom,
|
|
128
128
|
provider: 'twilio', timestamp: new Date(), fromMe: true,
|
|
129
129
|
processed: messageData.processed ?? false
|
|
130
130
|
});
|
|
131
|
+
parentId = pending?._id || null;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
let result;
|
|
@@ -136,18 +137,18 @@ class TwilioProvider extends MessageProvider {
|
|
|
136
137
|
result = await this.twilioClient.messages.create(sends[i]);
|
|
137
138
|
} catch (twilioErr) {
|
|
138
139
|
await recordDeliveryAttempt({
|
|
139
|
-
messageData, messageId:
|
|
140
|
+
messageData, messageId: parentId, kind, body: sends[i].body || null,
|
|
140
141
|
errorSource: 'twilio_sync',
|
|
141
142
|
errorCode: twilioErr.code, errorMessage: twilioErr.message
|
|
142
143
|
});
|
|
143
144
|
throw twilioErr;
|
|
144
145
|
}
|
|
145
|
-
await recordDeliveryAttempt({ messageData, messageId:
|
|
146
|
+
await recordDeliveryAttempt({ messageData, messageId: parentId, twilioResult: result, kind, body: sends[i].body || null });
|
|
146
147
|
if (i < sends.length - 1) await new Promise(r => setTimeout(r, 100));
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
if (
|
|
150
|
-
await this.messageStorage.finalizePendingMessage(
|
|
150
|
+
if (parentId && this.messageStorage?.finalizePendingMessage) {
|
|
151
|
+
await this.messageStorage.finalizePendingMessage(parentId, chunks ? null : result.sid, {
|
|
151
152
|
status: result.status?.toLowerCase() || null,
|
|
152
153
|
updatedAt: new Date()
|
|
153
154
|
});
|
|
@@ -82,7 +82,8 @@ class NexusMessaging {
|
|
|
82
82
|
|
|
83
83
|
this.scheduledMessageJob = new ScheduledMessageJob({
|
|
84
84
|
queueAdapter: this.queueAdapter,
|
|
85
|
-
sendMessage: this.sendMessage.bind(this)
|
|
85
|
+
sendMessage: this.sendMessage.bind(this),
|
|
86
|
+
requireMessageStorage: () => this.messageStorage
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
this.templateApprovalJob = new TemplateApprovalJob({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
|
+
const { isRetryableError } = require('../utils/retryUtils');
|
|
2
3
|
const { ScheduledMessage } = require('../models/agendaMessageModel');
|
|
3
4
|
const { BaseJob } = require('./BaseJob');
|
|
4
5
|
|
|
@@ -15,12 +16,13 @@ const TERMINAL_STATUSES = ['sent', 'cancelled'];
|
|
|
15
16
|
const STALE_SENDING_MS = 5 * 60 * 1000;
|
|
16
17
|
|
|
17
18
|
class ScheduledMessageJob extends BaseJob {
|
|
18
|
-
constructor({ queueAdapter, sendMessage } = {}) {
|
|
19
|
+
constructor({ queueAdapter, sendMessage, requireMessageStorage = null } = {}) {
|
|
19
20
|
super({ queueAdapter, queueName: QUEUE_NAME });
|
|
20
21
|
if (typeof sendMessage !== 'function') {
|
|
21
22
|
throw new Error('ScheduledMessageJob requires a sendMessage function');
|
|
22
23
|
}
|
|
23
24
|
this.sendMessage = sendMessage;
|
|
25
|
+
this.requireMessageStorage = requireMessageStorage;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
async schedule({ scheduledMessageId, sendTime }) {
|
|
@@ -90,6 +92,23 @@ class ScheduledMessageJob extends BaseJob {
|
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
let parentMessageId = msg.parentMessageId || null;
|
|
96
|
+
if (!parentMessageId) {
|
|
97
|
+
const storage = this.requireMessageStorage?.();
|
|
98
|
+
if (storage?.savePendingMessage) {
|
|
99
|
+
const parent = await storage.savePendingMessage({
|
|
100
|
+
code: msg.code, body: msg.message, fileUrl: msg.fileUrl, fileType: msg.fileType,
|
|
101
|
+
contentSid: msg.contentSid, variables: msg.variables,
|
|
102
|
+
fromMe: true, processed: true,
|
|
103
|
+
frontendId: msg.frontendId, triggeredBy: msg.triggeredBy
|
|
104
|
+
});
|
|
105
|
+
parentMessageId = parent?._id || null;
|
|
106
|
+
if (parentMessageId) {
|
|
107
|
+
await ScheduledMessage.updateOne({ _id: scheduledMessageId }, { $set: { parentMessageId } });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
try {
|
|
94
113
|
const result = await this.sendMessage({
|
|
95
114
|
code: msg.code,
|
|
@@ -98,7 +117,8 @@ class ScheduledMessageJob extends BaseJob {
|
|
|
98
117
|
fileType: msg.fileType,
|
|
99
118
|
contentSid: msg.contentSid,
|
|
100
119
|
variables: msg.variables,
|
|
101
|
-
hidePreview: msg.hidePreview
|
|
120
|
+
hidePreview: msg.hidePreview,
|
|
121
|
+
parentMessageId
|
|
102
122
|
});
|
|
103
123
|
|
|
104
124
|
const messageId = result?.messageId || result?.sid || null;
|
|
@@ -112,21 +132,23 @@ class ScheduledMessageJob extends BaseJob {
|
|
|
112
132
|
|
|
113
133
|
return { success: true, messageId };
|
|
114
134
|
} catch (error) {
|
|
135
|
+
const retryable = isRetryableError(error);
|
|
115
136
|
await ScheduledMessage.updateOne(
|
|
116
137
|
{ _id: scheduledMessageId },
|
|
117
138
|
{
|
|
118
139
|
$set: {
|
|
119
|
-
status: 'failed',
|
|
120
|
-
failedAt: new Date(),
|
|
140
|
+
status: retryable ? 'pending' : 'failed',
|
|
141
|
+
failedAt: retryable ? null : new Date(),
|
|
121
142
|
errorCode: error?.code || null,
|
|
122
143
|
errorMessage: error?.message || null
|
|
123
144
|
}
|
|
124
145
|
}
|
|
125
146
|
);
|
|
126
147
|
|
|
127
|
-
logger.error('[ScheduledMessageJob] Send failed', { scheduledMessageId, error: error.message });
|
|
148
|
+
logger.error('[ScheduledMessageJob] Send failed', { scheduledMessageId, retryable, error: error.message });
|
|
128
149
|
|
|
129
|
-
throw error;
|
|
150
|
+
if (retryable) throw error;
|
|
151
|
+
return { success: false, reason: 'non_retryable', error: error.message };
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
154
|
}
|
|
@@ -117,7 +117,7 @@ class TemplateApprovalJob extends BaseJob {
|
|
|
117
117
|
code: message.numero,
|
|
118
118
|
contentSid: templateSid,
|
|
119
119
|
variables: {},
|
|
120
|
-
|
|
120
|
+
parentMessageId: message._id
|
|
121
121
|
});
|
|
122
122
|
recoveryMessageId = sendResult?.messageId;
|
|
123
123
|
if (!recoveryMessageId) {
|
|
@@ -17,6 +17,7 @@ const scheduledMessageSchema = new mongoose.Schema({
|
|
|
17
17
|
frontendId: { type: String, default: null },
|
|
18
18
|
flowActionPayload: { type: mongoose.Schema.Types.Mixed, default: null },
|
|
19
19
|
triggeredBy: { type: String, default: null },
|
|
20
|
+
parentMessageId: { type: mongoose.Schema.Types.ObjectId, ref: 'Message', default: null },
|
|
20
21
|
createdAt: { type: Date, default: Date.now }
|
|
21
22
|
});
|
|
22
23
|
|
|
@@ -55,9 +55,18 @@ class MongoStorage {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async finalizePendingMessage(docId, sid, statusInfo = null) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
58
|
+
const set = {};
|
|
59
|
+
if (statusInfo) {
|
|
60
|
+
for (const [k, v] of Object.entries(statusInfo)) set[`statusInfo.${k}`] = v;
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(set).length) {
|
|
63
|
+
await Message.updateOne({ _id: docId }, { $set: set });
|
|
64
|
+
}
|
|
65
|
+
if (sid) {
|
|
66
|
+
await Message.updateOne(
|
|
67
|
+
{ _id: docId, message_id: null },
|
|
68
|
+
{ $set: { message_id: sid } }
|
|
69
|
+
);
|
|
61
70
|
}
|
|
62
71
|
safeEmit(getStatusEventBus(), 'message:status', createEvent('message:status', {
|
|
63
72
|
messageId: String(docId),
|