@app-connect/core 1.7.30 → 1.7.33
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/connector/developerPortal.js +21 -1
- package/docs/connectors.md +1 -1
- package/docs/handlers.md +11 -8
- package/docs/libraries.md +0 -3
- package/docs/models.md +5 -5
- package/docs/routes.md +5 -1
- package/handlers/log.js +431 -167
- package/handlers/plugin.js +0 -24
- package/handlers/user.js +96 -9
- package/index.js +58 -46
- package/mcp/README.md +8 -0
- package/mcp/mcpHandler.js +126 -21
- package/package.json +1 -1
- package/releaseNotes.json +24 -0
- package/test/handlers/log.test.js +225 -0
- package/test/handlers/plugin.test.js +0 -256
- package/test/index.test.js +31 -0
- package/test/mcp/mcpHandler.test.js +73 -0
package/handlers/log.js
CHANGED
|
@@ -13,10 +13,9 @@ const { Connector } = require('../models/dynamo/connectorSchema');
|
|
|
13
13
|
const moment = require('moment');
|
|
14
14
|
const { getMediaReaderLinkByPlatformMediaLink } = require('../lib/util');
|
|
15
15
|
const axios = require('axios');
|
|
16
|
-
const { getPluginsFromUserSettings } = require('../lib/util');
|
|
17
16
|
const logger = require('../lib/logger');
|
|
18
17
|
const { handleApiError, handleDatabaseError } = require('../lib/errorHandler');
|
|
19
|
-
const {
|
|
18
|
+
const { randomUUID } = require('crypto');
|
|
20
19
|
const { AccountDataModel } = require('../models/accountDataModel');
|
|
21
20
|
const pluginCore = require('./plugin');
|
|
22
21
|
const {
|
|
@@ -25,6 +24,10 @@ const {
|
|
|
25
24
|
findMatchingCallLog,
|
|
26
25
|
} = require('../lib/callLogLookup');
|
|
27
26
|
|
|
27
|
+
const ASYNC_PLUGIN_CACHE_KEY = 'asyncPluginTask';
|
|
28
|
+
const ASYNC_PLUGIN_CALLBACK_PATH = '/plugin/async-callback';
|
|
29
|
+
const ASYNC_PLUGIN_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
30
|
+
|
|
28
31
|
function mergePluginWarnings({ returnMessage, warningMessages }) {
|
|
29
32
|
if (!warningMessages.length) {
|
|
30
33
|
return returnMessage;
|
|
@@ -44,8 +47,400 @@ function mergePluginWarnings({ returnMessage, warningMessages }) {
|
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
function
|
|
48
|
-
|
|
50
|
+
function getAsyncPluginCallbackUrl(taskId) {
|
|
51
|
+
const appServer = (process.env.APP_SERVER || process.env.OVERRIDE_APP_SERVER || '').replace(/\/+$/, '');
|
|
52
|
+
return `${appServer}${ASYNC_PLUGIN_CALLBACK_PATH}/${taskId}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getAsyncPluginRequestData({ operation, incomingData }) {
|
|
56
|
+
if (operation === 'updateCallLog') {
|
|
57
|
+
return { logInfo: incomingData };
|
|
58
|
+
}
|
|
59
|
+
return incomingData;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildAsyncPluginTaskData({ taskId, callbackUrl, pluginId, platform, operation, user, incomingData, existingCallLog, hashedAccountId, isFromSSCL }) {
|
|
63
|
+
const logInfo = incomingData?.logInfo ?? incomingData ?? {};
|
|
64
|
+
return {
|
|
65
|
+
type: ASYNC_PLUGIN_CACHE_KEY,
|
|
66
|
+
asyncTaskId: taskId,
|
|
67
|
+
callbackUrl,
|
|
68
|
+
pluginId,
|
|
69
|
+
platform,
|
|
70
|
+
logType: 'call',
|
|
71
|
+
operation,
|
|
72
|
+
userId: user.id,
|
|
73
|
+
rcAccountId: user.rcAccountId,
|
|
74
|
+
sessionId: logInfo.sessionId ?? incomingData?.sessionId,
|
|
75
|
+
extensionNumber: getCallLogExtensionNumber(incomingData),
|
|
76
|
+
callLogId: existingCallLog?.id ?? logInfo.telephonySessionId ?? logInfo.id,
|
|
77
|
+
thirdPartyLogId: existingCallLog?.thirdPartyLogId,
|
|
78
|
+
contactId: existingCallLog?.contactId ?? incomingData?.contactId,
|
|
79
|
+
incomingData,
|
|
80
|
+
hashedAccountId,
|
|
81
|
+
isFromSSCL,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function splitCallPluginsByExecutionMode(callPlugins) {
|
|
86
|
+
return {
|
|
87
|
+
syncCallPlugins: callPlugins.filter(plugin => !plugin.data.isAsync),
|
|
88
|
+
asyncCallPlugins: callPlugins.filter(plugin => plugin.data.isAsync),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runSyncCallPlugins({ syncCallPlugins, incomingData, user, platform }) {
|
|
93
|
+
let processedIncomingData = incomingData;
|
|
94
|
+
for (const plugin of syncCallPlugins) {
|
|
95
|
+
const pluginId = plugin.id;
|
|
96
|
+
const pluginJwtToken = plugin.data.jwtToken;
|
|
97
|
+
const pluginManifest = plugin.data;
|
|
98
|
+
const pluginEndpointUrl = pluginManifest.endpointUrl;
|
|
99
|
+
if (!pluginEndpointUrl) {
|
|
100
|
+
throw new Error('Plugin URL is not set');
|
|
101
|
+
}
|
|
102
|
+
const userConfig = pluginCore.getPluginConfigFromUserSettings({ userSettings: user.userSettings, pluginId });
|
|
103
|
+
const processedResultResponse = await axios.post(pluginEndpointUrl, {
|
|
104
|
+
data: processedIncomingData,
|
|
105
|
+
config: userConfig,
|
|
106
|
+
}, {
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `Bearer ${pluginJwtToken}`,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const refreshedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: processedResultResponse.headers });
|
|
112
|
+
if (refreshedPluginJwtToken) {
|
|
113
|
+
pluginCore.persistPluginData({
|
|
114
|
+
rcAccountId: user.rcAccountId,
|
|
115
|
+
pluginId,
|
|
116
|
+
jwtToken: refreshedPluginJwtToken,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
processedIncomingData = processedResultResponse.data;
|
|
120
|
+
}
|
|
121
|
+
return processedIncomingData;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function markAsyncPluginTaskFailed(cache, message) {
|
|
125
|
+
if (!cache) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await cache.update({
|
|
129
|
+
status: 'failed',
|
|
130
|
+
data: {
|
|
131
|
+
...cache.data,
|
|
132
|
+
message,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function dispatchAsyncCallPlugin({ plugin, incomingData, user, platform, operation, existingCallLog, hashedAccountId, isFromSSCL }) {
|
|
138
|
+
const pluginId = plugin.id;
|
|
139
|
+
const taskId = randomUUID();
|
|
140
|
+
const callbackUrl = getAsyncPluginCallbackUrl(taskId);
|
|
141
|
+
const taskData = buildAsyncPluginTaskData({
|
|
142
|
+
taskId,
|
|
143
|
+
callbackUrl,
|
|
144
|
+
pluginId,
|
|
145
|
+
platform,
|
|
146
|
+
operation,
|
|
147
|
+
user,
|
|
148
|
+
incomingData,
|
|
149
|
+
existingCallLog,
|
|
150
|
+
hashedAccountId,
|
|
151
|
+
isFromSSCL,
|
|
152
|
+
});
|
|
153
|
+
let taskCache = null;
|
|
154
|
+
try {
|
|
155
|
+
taskCache = await CacheModel.create({
|
|
156
|
+
id: taskId,
|
|
157
|
+
status: 'pending',
|
|
158
|
+
userId: user.id,
|
|
159
|
+
cacheKey: `${ASYNC_PLUGIN_CACHE_KEY}-${pluginId}`,
|
|
160
|
+
data: taskData,
|
|
161
|
+
expiry: new Date(Date.now() + ASYNC_PLUGIN_CACHE_TTL_MS),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const pluginJwtToken = plugin.data.jwtToken;
|
|
165
|
+
const pluginEndpointUrl = plugin.data.endpointUrl;
|
|
166
|
+
if (!pluginEndpointUrl) {
|
|
167
|
+
throw new Error('Plugin URL is not set');
|
|
168
|
+
}
|
|
169
|
+
if (!plugin.data.tokenSyncUrl) {
|
|
170
|
+
throw new Error('Plugin token sync URL is not set');
|
|
171
|
+
}
|
|
172
|
+
const syncPluginTokenResponse = await axios.post(plugin.data.tokenSyncUrl, {},
|
|
173
|
+
{
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${pluginJwtToken}`,
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
const syncedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: syncPluginTokenResponse.headers });
|
|
180
|
+
await axios.post(pluginEndpointUrl, {
|
|
181
|
+
data: getAsyncPluginRequestData({ operation, incomingData }),
|
|
182
|
+
config: pluginCore.getPluginConfigFromUserSettings({ userSettings: user.userSettings, pluginId }),
|
|
183
|
+
asyncTaskId: taskId,
|
|
184
|
+
callbackUrl,
|
|
185
|
+
}, {
|
|
186
|
+
headers: {
|
|
187
|
+
Authorization: `Bearer ${syncedPluginJwtToken ?? pluginJwtToken}`,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
if (syncedPluginJwtToken) {
|
|
191
|
+
pluginCore.persistPluginData({
|
|
192
|
+
rcAccountId: user.rcAccountId,
|
|
193
|
+
pluginId,
|
|
194
|
+
jwtToken: syncedPluginJwtToken,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
logger.error('Error dispatching async plugin task', {
|
|
200
|
+
pluginId,
|
|
201
|
+
taskId,
|
|
202
|
+
stack: error.stack,
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
await markAsyncPluginTaskFailed(taskCache, error.message || 'Async plugin dispatch failed');
|
|
206
|
+
}
|
|
207
|
+
catch (cacheError) {
|
|
208
|
+
logger.error('Error marking async plugin task failed', {
|
|
209
|
+
pluginId,
|
|
210
|
+
taskId,
|
|
211
|
+
stack: cacheError.stack,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function dispatchAsyncCallPlugins({ asyncCallPlugins, incomingData, user, platform, operation, existingCallLog, hashedAccountId, isFromSSCL }) {
|
|
218
|
+
for (const plugin of asyncCallPlugins) {
|
|
219
|
+
await dispatchAsyncCallPlugin({
|
|
220
|
+
plugin,
|
|
221
|
+
incomingData,
|
|
222
|
+
user,
|
|
223
|
+
platform,
|
|
224
|
+
operation,
|
|
225
|
+
existingCallLog,
|
|
226
|
+
hashedAccountId,
|
|
227
|
+
isFromSSCL,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function getAuthenticatedCallLogContext({ platform, userId }) {
|
|
233
|
+
let user = await UserModel.findByPk(userId);
|
|
234
|
+
if (!user || !user.accessToken) {
|
|
235
|
+
throw new Error('User not found');
|
|
236
|
+
}
|
|
237
|
+
const platformModule = connectorRegistry.getConnector(platform);
|
|
238
|
+
const proxyId = user.platformAdditionalInfo?.proxyId;
|
|
239
|
+
let proxyConfig = null;
|
|
240
|
+
if (proxyId) {
|
|
241
|
+
proxyConfig = await Connector.getProxyConfig(proxyId);
|
|
242
|
+
}
|
|
243
|
+
const authType = await platformModule.getAuthType({ proxyId, proxyConfig });
|
|
244
|
+
let authHeader = '';
|
|
245
|
+
switch (authType) {
|
|
246
|
+
case 'oauth': {
|
|
247
|
+
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
248
|
+
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
249
|
+
if (!user) {
|
|
250
|
+
throw new Error('User session expired. Please connect again.');
|
|
251
|
+
}
|
|
252
|
+
authHeader = `Bearer ${user.accessToken}`;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 'apiKey': {
|
|
256
|
+
const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
|
|
257
|
+
authHeader = `Basic ${basicAuth}`;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { user, platformModule, authHeader, proxyConfig };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function appendCallbackNote(existingNote, callbackNote) {
|
|
265
|
+
return [existingNote, callbackNote].filter(note => !!note).join('\n\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getCallbackUpdateData({ incomingData, existingCallLog, appendedNote }) {
|
|
269
|
+
const logInfo = incomingData?.logInfo ?? incomingData ?? {};
|
|
270
|
+
return {
|
|
271
|
+
recordingLink: incomingData?.recordingLink ?? logInfo.recording?.link,
|
|
272
|
+
recordingDownloadLink: incomingData?.recordingDownloadLink,
|
|
273
|
+
subject: incomingData?.subject ?? logInfo.customSubject,
|
|
274
|
+
note: appendedNote,
|
|
275
|
+
startTime: incomingData?.startTime ?? logInfo.startTime,
|
|
276
|
+
duration: incomingData?.duration ?? logInfo.duration,
|
|
277
|
+
result: incomingData?.result ?? logInfo.result,
|
|
278
|
+
aiNote: incomingData?.aiNote,
|
|
279
|
+
transcript: incomingData?.transcript,
|
|
280
|
+
legs: incomingData?.legs ?? logInfo.legs ?? [],
|
|
281
|
+
direction: incomingData?.direction ?? logInfo.direction,
|
|
282
|
+
from: incomingData?.from ?? logInfo.from,
|
|
283
|
+
to: incomingData?.to ?? logInfo.to,
|
|
284
|
+
ringSenseTranscript: incomingData?.ringSenseTranscript,
|
|
285
|
+
ringSenseSummary: incomingData?.ringSenseSummary,
|
|
286
|
+
ringSenseAIScore: incomingData?.ringSenseAIScore,
|
|
287
|
+
ringSenseBulletedSummary: incomingData?.ringSenseBulletedSummary,
|
|
288
|
+
ringSenseLink: incomingData?.ringSenseLink,
|
|
289
|
+
callLog: {
|
|
290
|
+
sessionId: existingCallLog.sessionId,
|
|
291
|
+
startTime: incomingData?.startTime ?? logInfo.startTime,
|
|
292
|
+
duration: incomingData?.duration ?? logInfo.duration,
|
|
293
|
+
result: incomingData?.result ?? logInfo.result,
|
|
294
|
+
direction: incomingData?.direction ?? logInfo.direction,
|
|
295
|
+
from: incomingData?.from ?? logInfo.from,
|
|
296
|
+
to: incomingData?.to ?? logInfo.to,
|
|
297
|
+
legs: incomingData?.legs ?? logInfo.legs ?? [],
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function appendAsyncPluginNoteToCallLog({ taskCache, note }) {
|
|
303
|
+
const taskData = taskCache.data || {};
|
|
304
|
+
const where = {
|
|
305
|
+
...buildCallLogSessionWhere({
|
|
306
|
+
sessionId: taskData.sessionId,
|
|
307
|
+
extensionNumber: taskData.extensionNumber,
|
|
308
|
+
}),
|
|
309
|
+
platform: taskData.platform,
|
|
310
|
+
userId: taskData.userId,
|
|
311
|
+
};
|
|
312
|
+
const existingCallLog = await CallLogModel.findOne({ where });
|
|
313
|
+
if (!existingCallLog) {
|
|
314
|
+
throw new Error('Call log not found for async plugin task');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const {
|
|
318
|
+
user,
|
|
319
|
+
platformModule,
|
|
320
|
+
authHeader,
|
|
321
|
+
proxyConfig,
|
|
322
|
+
} = await getAuthenticatedCallLogContext({
|
|
323
|
+
platform: taskData.platform,
|
|
324
|
+
userId: taskData.userId,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const logFormat = platformModule.getLogFormatType ? platformModule.getLogFormatType(taskData.platform, proxyConfig) : LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT;
|
|
328
|
+
const getLogResult = await platformModule.getCallLog({
|
|
329
|
+
user,
|
|
330
|
+
telephonySessionId: existingCallLog.id,
|
|
331
|
+
callLogId: existingCallLog.thirdPartyLogId,
|
|
332
|
+
contactId: existingCallLog.contactId,
|
|
333
|
+
authHeader,
|
|
334
|
+
proxyConfig,
|
|
335
|
+
});
|
|
336
|
+
const existingBody = getLogResult?.callLogInfo?.fullBody || getLogResult?.callLogInfo?.note || '';
|
|
337
|
+
const existingNote = getLogResult?.callLogInfo?.note || '';
|
|
338
|
+
const appendedNote = appendCallbackNote(existingNote, note);
|
|
339
|
+
const updateData = getCallbackUpdateData({
|
|
340
|
+
incomingData: taskData.incomingData,
|
|
341
|
+
existingCallLog,
|
|
342
|
+
appendedNote,
|
|
343
|
+
});
|
|
344
|
+
const composedLogDetails = composeCallLog({
|
|
345
|
+
logFormat,
|
|
346
|
+
existingBody,
|
|
347
|
+
callLog: updateData.callLog,
|
|
348
|
+
contactInfo: null,
|
|
349
|
+
user,
|
|
350
|
+
note: updateData.note,
|
|
351
|
+
aiNote: updateData.aiNote,
|
|
352
|
+
transcript: updateData.transcript,
|
|
353
|
+
recordingLink: updateData.recordingLink,
|
|
354
|
+
subject: updateData.subject,
|
|
355
|
+
startTime: updateData.startTime,
|
|
356
|
+
duration: updateData.duration,
|
|
357
|
+
result: updateData.result,
|
|
358
|
+
ringSenseTranscript: updateData.ringSenseTranscript,
|
|
359
|
+
ringSenseSummary: updateData.ringSenseSummary,
|
|
360
|
+
ringSenseAIScore: updateData.ringSenseAIScore,
|
|
361
|
+
ringSenseBulletedSummary: updateData.ringSenseBulletedSummary,
|
|
362
|
+
ringSenseLink: updateData.ringSenseLink,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
await platformModule.updateCallLog({
|
|
366
|
+
user,
|
|
367
|
+
existingCallLog,
|
|
368
|
+
authHeader,
|
|
369
|
+
recordingLink: updateData.recordingLink,
|
|
370
|
+
recordingDownloadLink: updateData.recordingDownloadLink,
|
|
371
|
+
subject: updateData.subject,
|
|
372
|
+
note: updateData.note,
|
|
373
|
+
startTime: updateData.startTime,
|
|
374
|
+
duration: updateData.duration,
|
|
375
|
+
result: updateData.result,
|
|
376
|
+
aiNote: updateData.aiNote,
|
|
377
|
+
transcript: updateData.transcript,
|
|
378
|
+
legs: updateData.legs,
|
|
379
|
+
ringSenseTranscript: updateData.ringSenseTranscript,
|
|
380
|
+
ringSenseSummary: updateData.ringSenseSummary,
|
|
381
|
+
ringSenseAIScore: updateData.ringSenseAIScore,
|
|
382
|
+
ringSenseBulletedSummary: updateData.ringSenseBulletedSummary,
|
|
383
|
+
ringSenseLink: updateData.ringSenseLink,
|
|
384
|
+
composedLogDetails,
|
|
385
|
+
existingCallLogDetails: getLogResult?.callLogInfo?.fullLogResponse,
|
|
386
|
+
hashedAccountId: taskData.hashedAccountId,
|
|
387
|
+
isFromSSCL: taskData.isFromSSCL,
|
|
388
|
+
proxyConfig,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function handleAsyncPluginCallback({ taskId, body }) {
|
|
393
|
+
if (typeof body?.successful !== 'boolean') {
|
|
394
|
+
return {
|
|
395
|
+
statusCode: 400,
|
|
396
|
+
body: { successful: false, message: 'successful is required' },
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const taskCache = await CacheModel.findByPk(taskId);
|
|
400
|
+
if (!taskCache) {
|
|
401
|
+
return {
|
|
402
|
+
statusCode: 404,
|
|
403
|
+
body: { successful: false, message: 'Async task not found' },
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (taskCache.expiry && taskCache.expiry.getTime() <= Date.now()) {
|
|
407
|
+
await taskCache.destroy();
|
|
408
|
+
return {
|
|
409
|
+
statusCode: 404,
|
|
410
|
+
body: { successful: false, message: 'Async task not found' },
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!body.successful) {
|
|
415
|
+
await markAsyncPluginTaskFailed(taskCache, body.message || 'Async plugin callback failed');
|
|
416
|
+
return {
|
|
417
|
+
statusCode: 200,
|
|
418
|
+
body: { successful: true },
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
await appendAsyncPluginNoteToCallLog({
|
|
424
|
+
taskCache,
|
|
425
|
+
note: typeof body.note === 'string' ? body.note : '',
|
|
426
|
+
});
|
|
427
|
+
await taskCache.destroy();
|
|
428
|
+
return {
|
|
429
|
+
statusCode: 200,
|
|
430
|
+
body: { successful: true },
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
logger.error('Async plugin callback failed to update call log', {
|
|
435
|
+
taskId,
|
|
436
|
+
stack: error.stack,
|
|
437
|
+
});
|
|
438
|
+
await markAsyncPluginTaskFailed(taskCache, error.message || body.message || 'Async plugin callback failed');
|
|
439
|
+
return {
|
|
440
|
+
statusCode: 500,
|
|
441
|
+
body: { successful: false, message: error.message || 'Async plugin callback failed' },
|
|
442
|
+
};
|
|
443
|
+
}
|
|
49
444
|
}
|
|
50
445
|
|
|
51
446
|
async function createCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
@@ -151,85 +546,13 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
151
546
|
name: incomingData.contactName ?? ""
|
|
152
547
|
};
|
|
153
548
|
|
|
154
|
-
|
|
155
|
-
const pluginAsyncTaskIds = [];
|
|
156
549
|
const pluginWarnings = [];
|
|
157
550
|
// Plugins
|
|
158
551
|
const accountPlugins = await pluginCore.getPluginsFromRcAccountId({ rcAccountId: user.rcAccountId });
|
|
159
552
|
const callPlugins = accountPlugins.filter(plugin => plugin.data.supportedLogTypes.includes('call'));
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const pluginManifest = plugin.data;
|
|
164
|
-
const pluginEndpointUrl = pluginManifest.endpointUrl;
|
|
165
|
-
if (!pluginEndpointUrl) {
|
|
166
|
-
throw new Error('Plugin URL is not set');
|
|
167
|
-
}
|
|
168
|
-
const userConfig = pluginCore.getPluginConfigFromUserSettings({ userSettings: user.userSettings, pluginId });
|
|
169
|
-
if (plugin.data.isAsync) {
|
|
170
|
-
const asyncTaskId = `${userId}-${uuidv4()}`;
|
|
171
|
-
pluginAsyncTaskIds.push(asyncTaskId);
|
|
172
|
-
await CacheModel.create({
|
|
173
|
-
id: asyncTaskId,
|
|
174
|
-
status: 'initialized',
|
|
175
|
-
userId,
|
|
176
|
-
cacheKey: `pluginTask-${plugin.data.name}`,
|
|
177
|
-
expiry: moment().add(1, 'hour').toDate()
|
|
178
|
-
});
|
|
179
|
-
try {
|
|
180
|
-
const syncPluginTokenResponse = await axios.post(plugin.data.tokenSyncUrl, {},
|
|
181
|
-
{
|
|
182
|
-
headers: {
|
|
183
|
-
Authorization: `Bearer ${pluginJwtToken}`,
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
const syncedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: syncPluginTokenResponse.headers });
|
|
188
|
-
axios.post(pluginEndpointUrl, {
|
|
189
|
-
data: incomingData,
|
|
190
|
-
config: userConfig,
|
|
191
|
-
asyncTaskId
|
|
192
|
-
}, {
|
|
193
|
-
headers: {
|
|
194
|
-
Authorization: `Bearer ${syncedPluginJwtToken ?? pluginJwtToken}`,
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
if (syncedPluginJwtToken) {
|
|
198
|
-
pluginCore.persistPluginData({
|
|
199
|
-
rcAccountId: user.rcAccountId,
|
|
200
|
-
platformName: platform,
|
|
201
|
-
pluginId,
|
|
202
|
-
jwtToken: syncedPluginJwtToken,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
logger.error('Error syncing plugin JWT token', { stack: error.stack });
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
const processedResultResponse = await axios.post(pluginEndpointUrl, {
|
|
212
|
-
data: incomingData,
|
|
213
|
-
config: userConfig,
|
|
214
|
-
}, {
|
|
215
|
-
headers: {
|
|
216
|
-
Authorization: `Bearer ${pluginJwtToken}`,
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
const refreshedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: processedResultResponse.headers });
|
|
220
|
-
if (refreshedPluginJwtToken) {
|
|
221
|
-
pluginCore.persistPluginData({
|
|
222
|
-
rcAccountId: user.rcAccountId,
|
|
223
|
-
platformName: platform,
|
|
224
|
-
pluginId,
|
|
225
|
-
jwtToken: refreshedPluginJwtToken,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
// eslint-disable-next-line no-param-reassign
|
|
229
|
-
incomingData = processedResultResponse.data;
|
|
230
|
-
note = incomingData.note;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
553
|
+
const { syncCallPlugins, asyncCallPlugins } = splitCallPluginsByExecutionMode(callPlugins);
|
|
554
|
+
incomingData = await runSyncCallPlugins({ syncCallPlugins, incomingData, user, platform });
|
|
555
|
+
note = incomingData.note;
|
|
233
556
|
|
|
234
557
|
// Compose call log details centrally
|
|
235
558
|
const logFormat = platformModule.getLogFormatType ? platformModule.getLogFormatType(platform, proxyConfig) : LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT;
|
|
@@ -283,7 +606,7 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
283
606
|
extraDataTracking.withTranscript = !!transcript;
|
|
284
607
|
if (logId) {
|
|
285
608
|
try {
|
|
286
|
-
await CallLogModel.create({
|
|
609
|
+
const createdCallLog = await CallLogModel.create({
|
|
287
610
|
id: incomingData.logInfo.telephonySessionId || incomingData.logInfo.id,
|
|
288
611
|
sessionId: incomingData.logInfo.sessionId,
|
|
289
612
|
extensionNumber,
|
|
@@ -292,6 +615,18 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
292
615
|
userId,
|
|
293
616
|
contactId
|
|
294
617
|
});
|
|
618
|
+
if (asyncCallPlugins.length) {
|
|
619
|
+
await dispatchAsyncCallPlugins({
|
|
620
|
+
asyncCallPlugins,
|
|
621
|
+
incomingData,
|
|
622
|
+
user,
|
|
623
|
+
platform,
|
|
624
|
+
operation: 'createCallLog',
|
|
625
|
+
existingCallLog: createdCallLog,
|
|
626
|
+
hashedAccountId,
|
|
627
|
+
isFromSSCL,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
295
630
|
}
|
|
296
631
|
catch (error) {
|
|
297
632
|
return handleDatabaseError(error, 'Error creating call log');
|
|
@@ -300,8 +635,7 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
300
635
|
successful: !!logId,
|
|
301
636
|
logId,
|
|
302
637
|
returnMessage: mergePluginWarnings({ returnMessage, warningMessages: pluginWarnings }),
|
|
303
|
-
extraDataTracking
|
|
304
|
-
pluginAsyncTaskIds
|
|
638
|
+
extraDataTracking
|
|
305
639
|
};
|
|
306
640
|
}
|
|
307
641
|
else {
|
|
@@ -466,83 +800,12 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
466
800
|
break;
|
|
467
801
|
}
|
|
468
802
|
|
|
469
|
-
const pluginAsyncTaskIds = [];
|
|
470
803
|
const pluginWarnings = [];
|
|
471
804
|
// Plugins
|
|
472
805
|
const accountPlugins = await pluginCore.getPluginsFromRcAccountId({ rcAccountId: user.rcAccountId });
|
|
473
806
|
const callPlugins = accountPlugins.filter(plugin => plugin.data.supportedLogTypes.includes('call'));
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const pluginJwtToken = plugin.data.jwtToken;
|
|
477
|
-
const pluginManifest = plugin.data;
|
|
478
|
-
const pluginEndpointUrl = pluginManifest.endpointUrl;
|
|
479
|
-
if (!pluginEndpointUrl) {
|
|
480
|
-
throw new Error('Plugin URL is not set');
|
|
481
|
-
}
|
|
482
|
-
const userConfig = pluginCore.getPluginConfigFromUserSettings({ userSettings: user.userSettings, pluginId });
|
|
483
|
-
if (plugin.data.isAsync) {
|
|
484
|
-
const asyncTaskId = `${userId}-${uuidv4()}`;
|
|
485
|
-
pluginAsyncTaskIds.push(asyncTaskId);
|
|
486
|
-
await CacheModel.create({
|
|
487
|
-
id: asyncTaskId,
|
|
488
|
-
status: 'initialized',
|
|
489
|
-
userId,
|
|
490
|
-
cacheKey: `pluginTask-${plugin.data.name}`,
|
|
491
|
-
expiry: moment().add(1, 'hour').toDate()
|
|
492
|
-
});
|
|
493
|
-
try {
|
|
494
|
-
const syncPluginTokenResponse = await axios.post(plugin.data.tokenSyncUrl, {},
|
|
495
|
-
{
|
|
496
|
-
headers: {
|
|
497
|
-
Authorization: `Bearer ${pluginJwtToken}`,
|
|
498
|
-
},
|
|
499
|
-
}
|
|
500
|
-
);
|
|
501
|
-
const syncedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: syncPluginTokenResponse.headers });
|
|
502
|
-
axios.post(pluginEndpointUrl, {
|
|
503
|
-
data: { logInfo: incomingData },
|
|
504
|
-
config: userConfig,
|
|
505
|
-
asyncTaskId
|
|
506
|
-
}, {
|
|
507
|
-
headers: {
|
|
508
|
-
Authorization: `Bearer ${syncedPluginJwtToken ?? pluginJwtToken}`,
|
|
509
|
-
},
|
|
510
|
-
});
|
|
511
|
-
if (syncedPluginJwtToken) {
|
|
512
|
-
pluginCore.persistPluginData({
|
|
513
|
-
rcAccountId: user.rcAccountId,
|
|
514
|
-
platformName: platform,
|
|
515
|
-
pluginId,
|
|
516
|
-
jwtToken: syncedPluginJwtToken,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
catch (error) {
|
|
521
|
-
logger.error('Error syncing plugin JWT token', { stack: error.stack });
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
const processedResultResponse = await axios.post(pluginEndpointUrl, {
|
|
526
|
-
data: incomingData,
|
|
527
|
-
config: userConfig
|
|
528
|
-
}, {
|
|
529
|
-
headers: {
|
|
530
|
-
Authorization: `Bearer ${pluginJwtToken}`,
|
|
531
|
-
},
|
|
532
|
-
});
|
|
533
|
-
const refreshedPluginJwtToken = pluginCore.getRefreshedJwtTokenFromHeaders({ headers: processedResultResponse.headers });
|
|
534
|
-
if (refreshedPluginJwtToken) {
|
|
535
|
-
pluginCore.persistPluginData({
|
|
536
|
-
rcAccountId: user.rcAccountId,
|
|
537
|
-
platformName: platform,
|
|
538
|
-
pluginId,
|
|
539
|
-
jwtToken: refreshedPluginJwtToken,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
// eslint-disable-next-line no-param-reassign
|
|
543
|
-
incomingData = processedResultResponse.data;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
807
|
+
const { syncCallPlugins, asyncCallPlugins } = splitCallPluginsByExecutionMode(callPlugins);
|
|
808
|
+
incomingData = await runSyncCallPlugins({ syncCallPlugins, incomingData, user, platform });
|
|
546
809
|
|
|
547
810
|
// Fetch existing call log details once to avoid duplicate API calls
|
|
548
811
|
let existingCallLogDetails = null; // Compose updated call log details centrally
|
|
@@ -626,13 +889,24 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
626
889
|
isFromSSCL,
|
|
627
890
|
proxyConfig,
|
|
628
891
|
});
|
|
892
|
+
if (asyncCallPlugins.length) {
|
|
893
|
+
await dispatchAsyncCallPlugins({
|
|
894
|
+
asyncCallPlugins,
|
|
895
|
+
incomingData,
|
|
896
|
+
user,
|
|
897
|
+
platform,
|
|
898
|
+
operation: 'updateCallLog',
|
|
899
|
+
existingCallLog,
|
|
900
|
+
hashedAccountId,
|
|
901
|
+
isFromSSCL,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
629
904
|
return {
|
|
630
905
|
successful: true,
|
|
631
906
|
logId: existingCallLog.thirdPartyLogId,
|
|
632
907
|
updatedNote,
|
|
633
908
|
returnMessage: mergePluginWarnings({ returnMessage, warningMessages: pluginWarnings }),
|
|
634
909
|
extraDataTracking,
|
|
635
|
-
pluginAsyncTaskIds
|
|
636
910
|
};
|
|
637
911
|
}
|
|
638
912
|
return { successful: false };
|
|
@@ -730,7 +1004,6 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
730
1004
|
const ownerName = incomingData.logInfo.owner?.name;
|
|
731
1005
|
const isSharedSMS = !!ownerName;
|
|
732
1006
|
|
|
733
|
-
const pluginAsyncTaskIds = [];
|
|
734
1007
|
const pluginWarnings = [];
|
|
735
1008
|
// Plugins
|
|
736
1009
|
const isSMS = incomingData.logInfo.messages.some(m => m.type === 'SMS');
|
|
@@ -749,15 +1022,6 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
749
1022
|
}
|
|
750
1023
|
const userConfig = pluginCore.getPluginConfigFromUserSettings({ userSettings: user.userSettings, pluginId });
|
|
751
1024
|
if (plugin.data.isAsync) {
|
|
752
|
-
const asyncTaskId = `${userId}-${uuidv4()}`;
|
|
753
|
-
pluginAsyncTaskIds.push(asyncTaskId);
|
|
754
|
-
await CacheModel.create({
|
|
755
|
-
id: asyncTaskId,
|
|
756
|
-
status: 'initialized',
|
|
757
|
-
userId,
|
|
758
|
-
cacheKey: `pluginTask-${plugin.data.name}`,
|
|
759
|
-
expiry: moment().add(1, 'hour').toDate()
|
|
760
|
-
});
|
|
761
1025
|
try {
|
|
762
1026
|
const syncPluginTokenResponse = await axios.post(plugin.data.tokenSyncUrl, {},
|
|
763
1027
|
{
|
|
@@ -770,7 +1034,6 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
770
1034
|
axios.post(pluginEndpointUrl, {
|
|
771
1035
|
data: { logInfo: incomingData },
|
|
772
1036
|
config: userConfig,
|
|
773
|
-
asyncTaskId
|
|
774
1037
|
}, {
|
|
775
1038
|
headers: {
|
|
776
1039
|
Authorization: `Bearer ${syncedPluginJwtToken ?? pluginJwtToken}`,
|
|
@@ -988,3 +1251,4 @@ exports.updateCallLog = updateCallLog;
|
|
|
988
1251
|
exports.createMessageLog = createMessageLog;
|
|
989
1252
|
exports.getCallLog = getCallLog;
|
|
990
1253
|
exports.saveNoteCache = saveNoteCache;
|
|
1254
|
+
exports.handleAsyncPluginCallback = handleAsyncPluginCallback;
|