@app-connect/core 1.7.32 → 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/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 { v4: uuidv4 } = require('uuid');
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 getPluginWarningMessage({ pluginId }) {
48
- return `Plugin ${pluginId} skipped: missing account-level plugin jwtToken. Reinstall or re-register plugin.`;
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
- for (const plugin of callPlugins) {
161
- const pluginId = plugin.id;
162
- const pluginJwtToken = plugin.data.jwtToken;
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
- for (const plugin of callPlugins) {
475
- const pluginId = plugin.id;
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;