@app-connect/core 1.5.8 → 1.6.4

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
@@ -1,587 +1,612 @@
1
- const Op = require('sequelize').Op;
2
- const { CallLogModel } = require('../models/callLogModel');
3
- const { MessageLogModel } = require('../models/messageLogModel');
4
- const { UserModel } = require('../models/userModel');
5
- const oauth = require('../lib/oauth');
6
- const errorMessage = require('../lib/generalErrorMessage');
7
- const { composeCallLog, getLogFormatType } = require('../lib/callLogComposer');
8
- const adapterRegistry = require('../adapter/registry');
9
- const { LOG_DETAILS_FORMAT_TYPE } = require('../lib/constants');
10
-
11
- async function createCallLog({ platform, userId, incomingData }) {
12
- try {
13
- const existingCallLog = await CallLogModel.findOne({
14
- where: {
15
- sessionId: incomingData.logInfo.sessionId
16
- }
17
- });
18
- if (existingCallLog) {
19
- return {
20
- successful: false,
21
- returnMessage: {
22
- message: `Existing log for session ${incomingData.logInfo.sessionId}`,
23
- messageType: 'warning',
24
- ttl: 3000
25
- }
26
- }
27
- }
28
- let user = await UserModel.findByPk(userId);
29
- if (!user || !user.accessToken) {
30
- return {
31
- successful: false,
32
- returnMessage: {
33
- message: `User not found`,
34
- messageType: 'warning',
35
- ttl: 5000
36
- }
37
- };
38
- }
39
- const platformModule = adapterRegistry.getAdapter(platform);
40
- const callLog = incomingData.logInfo;
41
- const additionalSubmission = incomingData.additionalSubmission;
42
- const note = incomingData.note;
43
- const aiNote = incomingData.aiNote;
44
- const transcript = incomingData.transcript;
45
- const authType = platformModule.getAuthType();
46
- let authHeader = '';
47
- switch (authType) {
48
- case 'oauth':
49
- const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
50
- user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
51
- authHeader = `Bearer ${user.accessToken}`;
52
- break;
53
- case 'apiKey':
54
- const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
55
- authHeader = `Basic ${basicAuth}`;
56
- break;
57
- }
58
- const contactNumber = callLog.direction === 'Inbound' ? callLog.from.phoneNumber : callLog.to.phoneNumber;
59
- const contactId = incomingData.contactId;
60
- if (!contactId || contactId === 0) {
61
- return {
62
- successful: false,
63
- returnMessage: {
64
- message: `Contact not found for number ${contactNumber}`,
65
- messageType: 'warning',
66
- ttl: 5000
67
- }
68
- };
69
- }
70
- const contactInfo = {
71
- id: contactId,
72
- phoneNumber: contactNumber,
73
- type: incomingData.contactType ?? "",
74
- name: incomingData.contactName ?? ""
75
- };
76
-
77
- // Compose call log details centrally
78
- const logFormat = getLogFormatType(platform);
79
- let composedLogDetails = '';
80
- if (logFormat === LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT || logFormat === LOG_DETAILS_FORMAT_TYPE.HTML || logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
81
- composedLogDetails = await composeCallLog({
82
- logFormat,
83
- callLog,
84
- contactInfo,
85
- user,
86
- note,
87
- aiNote,
88
- transcript,
89
- recordingLink: callLog.recording?.link,
90
- subject: callLog.customSubject,
91
- startTime: callLog.startTime,
92
- duration: callLog.duration,
93
- result: callLog.result,
94
- platform
95
- });
96
- }
97
-
98
- const { logId, returnMessage, extraDataTracking } = await platformModule.createCallLog({
99
- user,
100
- contactInfo,
101
- authHeader,
102
- callLog,
103
- note,
104
- additionalSubmission,
105
- aiNote,
106
- transcript,
107
- composedLogDetails
108
- });
109
- if (logId) {
110
- await CallLogModel.create({
111
- id: incomingData.logInfo.telephonySessionId || incomingData.logInfo.id,
112
- sessionId: incomingData.logInfo.sessionId,
113
- platform,
114
- thirdPartyLogId: logId,
115
- userId,
116
- contactId
117
- });
118
- }
119
- return { successful: !!logId, logId, returnMessage, extraDataTracking };
120
- } catch (e) {
121
- console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
122
- if (e.response?.status === 429) {
123
- return {
124
- successful: false,
125
- returnMessage: errorMessage.rateLimitErrorMessage({ platform })
126
- };
127
- }
128
- else if (e.response?.status >= 400 && e.response?.status < 410) {
129
- return {
130
- successful: false,
131
- returnMessage: errorMessage.authorizationErrorMessage({ platform }),
132
- extraDataTracking: {
133
- statusCode: e.response?.status,
134
- }
135
- };
136
- }
137
- return {
138
- successful: false,
139
- returnMessage:
140
- {
141
- message: `Error creating call log`,
142
- messageType: 'warning',
143
- details: [
144
- {
145
- title: 'Details',
146
- items: [
147
- {
148
- id: '1',
149
- type: 'text',
150
- text: `Please check if your account has permission to CREATE logs.`
151
- }
152
- ]
153
- }
154
- ],
155
- ttl: 5000
156
- }
157
- };
158
- }
159
- }
160
-
161
- async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
162
- try {
163
- let user = await UserModel.findByPk(userId);
164
- if (!user || !user.accessToken) {
165
- return { successful: false, message: `Contact not found` };
166
- }
167
- let logs = [];
168
- let returnMessage = null;
169
- let extraDataTracking = {};;
170
-
171
- // Handle undefined or null sessionIds
172
- if (!sessionIds) {
173
- return { successful: false, message: `No session IDs provided` };
174
- }
175
-
176
- let sessionIdsArray = sessionIds.split(',');
177
- if (sessionIdsArray.length > 5) {
178
- sessionIdsArray = sessionIdsArray.slice(0, 5);
179
- }
180
- if (requireDetails) {
181
- const platformModule = adapterRegistry.getAdapter(platform);
182
- const authType = platformModule.getAuthType();
183
- let authHeader = '';
184
- switch (authType) {
185
- case 'oauth':
186
- const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
187
- user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
188
- authHeader = `Bearer ${user.accessToken}`;
189
- break;
190
- case 'apiKey':
191
- const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
192
- authHeader = `Basic ${basicAuth}`;
193
- break;
194
- }
195
- const callLogs = await CallLogModel.findAll({
196
- where: {
197
- sessionId: {
198
- [Op.in]: sessionIdsArray
199
- }
200
- }
201
- });
202
- for (const sId of sessionIdsArray) {
203
- const callLog = callLogs.find(c => c.sessionId === sId);
204
- if (!callLog) {
205
- logs.push({ sessionId: sId, matched: false });
206
- }
207
- else {
208
- const getCallLogResult = await platformModule.getCallLog({ user, callLogId: callLog.thirdPartyLogId, contactId: callLog.contactId, authHeader });
209
- returnMessage = getCallLogResult.returnMessage;
210
- extraDataTracking = getCallLogResult.extraDataTracking;
211
- logs.push({ sessionId: callLog.sessionId, matched: true, logId: callLog.thirdPartyLogId, logData: getCallLogResult.callLogInfo });
212
- }
213
- }
214
- }
215
- else {
216
- const callLogs = await CallLogModel.findAll({
217
- where: {
218
- sessionId: {
219
- [Op.in]: sessionIdsArray
220
- }
221
- }
222
- });
223
- for (const sId of sessionIdsArray) {
224
- const callLog = callLogs.find(c => c.sessionId === sId);
225
- if (!callLog) {
226
- logs.push({ sessionId: sId, matched: false });
227
- }
228
- else {
229
- logs.push({ sessionId: callLog.sessionId, matched: true, logId: callLog.thirdPartyLogId });
230
- }
231
- }
232
- }
233
- return { successful: true, logs, returnMessage, extraDataTracking };
234
- }
235
- catch (e) {
236
- console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
237
- if (e.response?.status === 429) {
238
- return {
239
- successful: false,
240
- returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
241
- extraDataTracking: {
242
- statusCode: e.response?.status,
243
- }
244
- };
245
- }
246
- else if (e.response?.status >= 400 && e.response?.status < 410) {
247
- return {
248
- successful: false,
249
- returnMessage: errorMessage.authorizationErrorMessage({ platform }),
250
- extraDataTracking: {
251
- statusCode: e.response?.status,
252
- }
253
- };
254
- }
255
- return {
256
- successful: false,
257
- returnMessage:
258
- {
259
- message: `Error getting call log`,
260
- messageType: 'warning',
261
- details: [
262
- {
263
- title: 'Details',
264
- items: [
265
- {
266
- id: '1',
267
- type: 'text',
268
- text: `Please check if your account has permission to CREATE logs.`
269
- }
270
- ]
271
- }
272
- ],
273
- ttl: 5000
274
- },
275
- extraDataTracking: {
276
- statusCode: e.response?.status,
277
- }
278
- };
279
- }
280
- }
281
-
282
- async function updateCallLog({ platform, userId, incomingData }) {
283
- try {
284
- const existingCallLog = await CallLogModel.findOne({
285
- where: {
286
- sessionId: incomingData.sessionId
287
- }
288
- });
289
- if (existingCallLog) {
290
- const platformModule = adapterRegistry.getAdapter(platform);
291
- let user = await UserModel.findByPk(userId);
292
- if (!user || !user.accessToken) {
293
- return { successful: false, message: `Contact not found` };
294
- }
295
- const authType = platformModule.getAuthType();
296
- let authHeader = '';
297
- switch (authType) {
298
- case 'oauth':
299
- const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
300
- user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
301
- authHeader = `Bearer ${user.accessToken}`;
302
- break;
303
- case 'apiKey':
304
- const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
305
- authHeader = `Basic ${basicAuth}`;
306
- break;
307
- }
308
-
309
- // Fetch existing call log details once to avoid duplicate API calls
310
- let existingCallLogDetails = null; // Compose updated call log details centrally
311
- const logFormat = getLogFormatType(platform);
312
- let composedLogDetails = '';
313
- if (logFormat === LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT || logFormat === LOG_DETAILS_FORMAT_TYPE.HTML || logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
314
- let existingBody = '';
315
- try {
316
- const getLogResult = await platformModule.getCallLog({
317
- user,
318
- callLogId: existingCallLog.thirdPartyLogId,
319
- authHeader
320
- });
321
- existingCallLogDetails = getLogResult?.callLogInfo?.fullLogResponse;
322
- // Extract existing body from the platform-specific response
323
- if (getLogResult.callLogInfo?.fullBody) {
324
- existingBody = getLogResult.callLogInfo.fullBody;
325
- } else if (getLogResult.callLogInfo?.note) {
326
- existingBody = getLogResult.callLogInfo.note;
327
- }
328
- } catch (error) {
329
- console.log('Error getting existing log details, proceeding with empty body', error);
330
- }
331
- composedLogDetails = await composeCallLog({
332
- logFormat,
333
- existingBody,
334
- callLog: {
335
- sessionId: existingCallLog.sessionId,
336
- startTime: incomingData.startTime,
337
- duration: incomingData.duration,
338
- result: incomingData.result
339
- },
340
- contactInfo: null, // Not needed for updates
341
- user,
342
- note: incomingData.note,
343
- aiNote: incomingData.aiNote,
344
- transcript: incomingData.transcript,
345
- recordingLink: incomingData.recordingLink,
346
- subject: incomingData.subject,
347
- startTime: incomingData.startTime,
348
- duration: incomingData.duration,
349
- result: incomingData.result,
350
- });
351
- }
352
-
353
- const { updatedNote, returnMessage, extraDataTracking } = await platformModule.updateCallLog({
354
- user,
355
- existingCallLog,
356
- authHeader,
357
- recordingLink: incomingData.recordingLink,
358
- recordingDownloadLink: incomingData.recordingDownloadLink,
359
- subject: incomingData.subject,
360
- note: incomingData.note,
361
- startTime: incomingData.startTime,
362
- duration: incomingData.duration,
363
- result: incomingData.result,
364
- aiNote: incomingData.aiNote,
365
- transcript: incomingData.transcript,
366
- additionalSubmission: incomingData.additionalSubmission,
367
- composedLogDetails,
368
- existingCallLogDetails // Pass the fetched details to avoid duplicate API calls
369
- });
370
- return { successful: true, logId: existingCallLog.thirdPartyLogId, updatedNote, returnMessage, extraDataTracking };
371
- }
372
- return { successful: false };
373
- } catch (e) {
374
- console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
375
- if (e.response?.status === 429) {
376
- return {
377
- successful: false,
378
- returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
379
- extraDataTracking: {
380
- statusCode: e.response?.status,
381
- }
382
- };
383
- }
384
- else if (e.response?.status >= 400 && e.response?.status < 410) {
385
- return {
386
- successful: false,
387
- returnMessage: errorMessage.authorizationErrorMessage({ platform }),
388
- extraDataTracking: {
389
- statusCode: e.response?.status,
390
- }
391
- };
392
- }
393
- return {
394
- successful: false,
395
- returnMessage:
396
- {
397
- message: `Error updating call log`,
398
- messageType: 'warning',
399
- details: [
400
- {
401
- title: 'Details',
402
- items: [
403
- {
404
- id: '1',
405
- type: 'text',
406
- text: `Please check if the log entity still exist on ${platform} and your account has permission to EDIT logs.`
407
- }
408
- ]
409
- }
410
- ],
411
- ttl: 5000
412
- },
413
- extraDataTracking: {
414
- statusCode: e.response?.status,
415
- }
416
- };
417
- }
418
- }
419
-
420
- async function createMessageLog({ platform, userId, incomingData }) {
421
- try {
422
- let returnMessage = null;
423
- let extraDataTracking = {};;
424
- if (incomingData.logInfo.messages.length === 0) {
425
- return {
426
- successful: false,
427
- returnMessage:
428
- {
429
- message: 'No message to log.',
430
- messageType: 'warning',
431
- ttl: 3000
432
- }
433
- }
434
- }
435
- const platformModule = adapterRegistry.getAdapter(platform);
436
- const contactNumber = incomingData.logInfo.correspondents[0].phoneNumber;
437
- const additionalSubmission = incomingData.additionalSubmission;
438
- let user = await UserModel.findByPk(userId);
439
- if (!user || !user.accessToken) {
440
- return {
441
- successful: false,
442
- returnMessage:
443
- {
444
- message: `Contact not found`,
445
- messageType: 'warning',
446
- ttl: 5000
447
- }
448
- };
449
- }
450
- const authType = platformModule.getAuthType();
451
- let authHeader = '';
452
- switch (authType) {
453
- case 'oauth':
454
- const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
455
- user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
456
- authHeader = `Bearer ${user.accessToken}`;
457
- break;
458
- case 'apiKey':
459
- const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
460
- authHeader = `Basic ${basicAuth}`;
461
- break;
462
- }
463
- const contactId = incomingData.contactId;
464
- if (!contactId) {
465
- return {
466
- successful: false,
467
- returnMessage:
468
- {
469
- message: `Contact not found for number ${contactNumber}`,
470
- messageType: 'warning',
471
- ttl: 5000
472
- }
473
- };
474
- }
475
- const contactInfo = {
476
- id: contactId,
477
- phoneNumber: contactNumber,
478
- type: incomingData.contactType ?? "",
479
- name: incomingData.contactName ?? ""
480
- };
481
- const messageIds = incomingData.logInfo.messages.map(m => { return { id: m.id.toString() }; });
482
- const existingMessages = await MessageLogModel.findAll({
483
- where: {
484
- [Op.or]: messageIds
485
- }
486
- });
487
- const existingIds = existingMessages.map(m => m.id);
488
- const logIds = [];
489
- // reverse the order of messages to log the oldest message first
490
- const reversedMessages = incomingData.logInfo.messages.reverse();
491
- for (const message of reversedMessages) {
492
- if (existingIds.includes(message.id.toString())) {
493
- continue;
494
- }
495
- let recordingLink = null;
496
- if (message.attachments && message.attachments.some(a => a.type === 'AudioRecording')) {
497
- recordingLink = message.attachments.find(a => a.type === 'AudioRecording').link;
498
- }
499
- let faxDocLink = null;
500
- let faxDownloadLink = null;
501
- if (message.attachments && message.attachments.some(a => a.type === 'RenderedDocument')) {
502
- faxDocLink = message.attachments.find(a => a.type === 'RenderedDocument').link;
503
- faxDownloadLink = message.attachments.find(a => a.type === 'RenderedDocument').uri + `?access_token=${incomingData.logInfo.rcAccessToken}`
504
- }
505
- const existingSameDateMessageLog = await MessageLogModel.findOne({
506
- where: {
507
- conversationLogId: incomingData.logInfo.conversationLogId
508
- }
509
- });
510
- let crmLogId = ''
511
- if (existingSameDateMessageLog) {
512
- const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission });
513
- crmLogId = existingSameDateMessageLog.thirdPartyLogId;
514
- returnMessage = updateMessageResult?.returnMessage;
515
- }
516
- else {
517
- const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink });
518
- crmLogId = createMessageLogResult.logId;
519
- returnMessage = createMessageLogResult?.returnMessage;
520
- extraDataTracking = createMessageLogResult.extraDataTracking;
521
- }
522
- if (crmLogId) {
523
- const createdMessageLog =
524
- await MessageLogModel.create({
525
- id: message.id.toString(),
526
- platform,
527
- conversationId: incomingData.logInfo.conversationId,
528
- thirdPartyLogId: crmLogId,
529
- userId,
530
- conversationLogId: incomingData.logInfo.conversationLogId
531
- });
532
- logIds.push(createdMessageLog.id);
533
- }
534
- }
535
- return { successful: true, logIds, returnMessage, extraDataTracking };
536
- }
537
- catch (e) {
538
- console.log(`platform: ${platform} \n${e.stack}`);
539
- if (e.response?.status === 429) {
540
- return {
541
- successful: false,
542
- returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
543
- extraDataTracking: {
544
- statusCode: e.response?.status,
545
- }
546
- };
547
- }
548
- else if (e.response?.status >= 400 && e.response?.status < 410) {
549
- return {
550
- successful: false,
551
- returnMessage: errorMessage.authorizationErrorMessage({ platform }),
552
- extraDataTracking: {
553
- statusCode: e.response?.status,
554
- }
555
- };
556
- }
557
- return {
558
- successful: false,
559
- returnMessage:
560
- {
561
- message: `Error creating message log`,
562
- messageType: 'warning',
563
- details: [
564
- {
565
- title: 'Details',
566
- items: [
567
- {
568
- id: '1',
569
- type: 'text',
570
- text: `Please check if your account has permission to CREATE logs.`
571
- }
572
- ]
573
- }
574
- ],
575
- ttl: 5000
576
- },
577
- extraDataTracking: {
578
- statusCode: e.response?.status,
579
- }
580
- };
581
- }
582
- }
583
-
584
- exports.createCallLog = createCallLog;
585
- exports.updateCallLog = updateCallLog;
586
- exports.createMessageLog = createMessageLog;
587
- exports.getCallLog = getCallLog;
1
+ const Op = require('sequelize').Op;
2
+ const { CallLogModel } = require('../models/callLogModel');
3
+ const { MessageLogModel } = require('../models/messageLogModel');
4
+ const { UserModel } = require('../models/userModel');
5
+ const oauth = require('../lib/oauth');
6
+ const errorMessage = require('../lib/generalErrorMessage');
7
+ const { composeCallLog, getLogFormatType } = require('../lib/callLogComposer');
8
+ const adapterRegistry = require('../adapter/registry');
9
+ const { LOG_DETAILS_FORMAT_TYPE } = require('../lib/constants');
10
+ const { NoteCache } = require('../models/dynamo/noteCacheSchema');
11
+ const moment = require('moment');
12
+
13
+ async function createCallLog({ platform, userId, incomingData, isFromSSCL }) {
14
+ try {
15
+ const existingCallLog = await CallLogModel.findOne({
16
+ where: {
17
+ sessionId: incomingData.logInfo.sessionId
18
+ }
19
+ });
20
+ if (existingCallLog) {
21
+ return {
22
+ successful: false,
23
+ returnMessage: {
24
+ message: `Existing log for session ${incomingData.logInfo.sessionId}`,
25
+ messageType: 'warning',
26
+ ttl: 3000
27
+ }
28
+ }
29
+ }
30
+ let user = await UserModel.findByPk(userId);
31
+ if (!user || !user.accessToken) {
32
+ return {
33
+ successful: false,
34
+ returnMessage: {
35
+ message: `User not found`,
36
+ messageType: 'warning',
37
+ ttl: 5000
38
+ }
39
+ };
40
+ }
41
+ const platformModule = adapterRegistry.getAdapter(platform);
42
+ const callLog = incomingData.logInfo;
43
+ const additionalSubmission = incomingData.additionalSubmission;
44
+ let note = incomingData.note;
45
+ if (isFromSSCL) {
46
+ const noteCache = await NoteCache.get({ sessionId: incomingData.logInfo.sessionId });
47
+ if (noteCache) {
48
+ note = noteCache.note;
49
+ }
50
+ }
51
+ const aiNote = incomingData.aiNote;
52
+ const transcript = incomingData.transcript;
53
+ const authType = platformModule.getAuthType();
54
+ let authHeader = '';
55
+ switch (authType) {
56
+ case 'oauth':
57
+ const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
58
+ user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
59
+ authHeader = `Bearer ${user.accessToken}`;
60
+ break;
61
+ case 'apiKey':
62
+ const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
63
+ authHeader = `Basic ${basicAuth}`;
64
+ break;
65
+ }
66
+ const contactNumber = callLog.direction === 'Inbound' ? callLog.from.phoneNumber : callLog.to.phoneNumber;
67
+ const contactId = incomingData.contactId;
68
+ if (!contactId || contactId === 0) {
69
+ return {
70
+ successful: false,
71
+ returnMessage: {
72
+ message: `Contact not found for number ${contactNumber}`,
73
+ messageType: 'warning',
74
+ ttl: 5000
75
+ }
76
+ };
77
+ }
78
+ const contactInfo = {
79
+ id: contactId,
80
+ phoneNumber: contactNumber,
81
+ type: incomingData.contactType ?? "",
82
+ name: incomingData.contactName ?? ""
83
+ };
84
+
85
+ // Compose call log details centrally
86
+ const logFormat = getLogFormatType(platform);
87
+ let composedLogDetails = '';
88
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT || logFormat === LOG_DETAILS_FORMAT_TYPE.HTML || logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
89
+ composedLogDetails = await composeCallLog({
90
+ logFormat,
91
+ callLog,
92
+ contactInfo,
93
+ user,
94
+ note,
95
+ aiNote,
96
+ transcript,
97
+ recordingLink: callLog.recording?.link,
98
+ subject: callLog.customSubject,
99
+ startTime: callLog.startTime,
100
+ duration: callLog.duration,
101
+ result: callLog.result,
102
+ platform
103
+ });
104
+ }
105
+
106
+ const { logId, returnMessage, extraDataTracking } = await platformModule.createCallLog({
107
+ user,
108
+ contactInfo,
109
+ authHeader,
110
+ callLog,
111
+ note,
112
+ additionalSubmission,
113
+ aiNote,
114
+ transcript,
115
+ composedLogDetails,
116
+ isFromSSCL
117
+ });
118
+ if (logId) {
119
+ await CallLogModel.create({
120
+ id: incomingData.logInfo.telephonySessionId || incomingData.logInfo.id,
121
+ sessionId: incomingData.logInfo.sessionId,
122
+ platform,
123
+ thirdPartyLogId: logId,
124
+ userId,
125
+ contactId
126
+ });
127
+ }
128
+ return { successful: !!logId, logId, returnMessage, extraDataTracking };
129
+ } catch (e) {
130
+ console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
131
+ if (e.response?.status === 429) {
132
+ return {
133
+ successful: false,
134
+ returnMessage: errorMessage.rateLimitErrorMessage({ platform })
135
+ };
136
+ }
137
+ else if (e.response?.status >= 400 && e.response?.status < 410) {
138
+ return {
139
+ successful: false,
140
+ returnMessage: errorMessage.authorizationErrorMessage({ platform }),
141
+ extraDataTracking: {
142
+ statusCode: e.response?.status,
143
+ }
144
+ };
145
+ }
146
+ return {
147
+ successful: false,
148
+ returnMessage:
149
+ {
150
+ message: `Error creating call log`,
151
+ messageType: 'warning',
152
+ details: [
153
+ {
154
+ title: 'Details',
155
+ items: [
156
+ {
157
+ id: '1',
158
+ type: 'text',
159
+ text: `Please check if your account has permission to CREATE logs.`
160
+ }
161
+ ]
162
+ }
163
+ ],
164
+ ttl: 5000
165
+ }
166
+ };
167
+ }
168
+ }
169
+
170
+ async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
171
+ try {
172
+ let user = await UserModel.findByPk(userId);
173
+ if (!user || !user.accessToken) {
174
+ return { successful: false, message: `Contact not found` };
175
+ }
176
+ let logs = [];
177
+ let returnMessage = null;
178
+ let extraDataTracking = {};;
179
+
180
+ // Handle undefined or null sessionIds
181
+ if (!sessionIds) {
182
+ return { successful: false, message: `No session IDs provided` };
183
+ }
184
+
185
+ let sessionIdsArray = sessionIds.split(',');
186
+ if (sessionIdsArray.length > 5) {
187
+ sessionIdsArray = sessionIdsArray.slice(0, 5);
188
+ }
189
+ if (requireDetails) {
190
+ const platformModule = adapterRegistry.getAdapter(platform);
191
+ const authType = platformModule.getAuthType();
192
+ let authHeader = '';
193
+ switch (authType) {
194
+ case 'oauth':
195
+ const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
196
+ user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
197
+ authHeader = `Bearer ${user.accessToken}`;
198
+ break;
199
+ case 'apiKey':
200
+ const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
201
+ authHeader = `Basic ${basicAuth}`;
202
+ break;
203
+ }
204
+ const callLogs = await CallLogModel.findAll({
205
+ where: {
206
+ sessionId: {
207
+ [Op.in]: sessionIdsArray
208
+ }
209
+ }
210
+ });
211
+ for (const sId of sessionIdsArray) {
212
+ const callLog = callLogs.find(c => c.sessionId === sId);
213
+ if (!callLog) {
214
+ logs.push({ sessionId: sId, matched: false });
215
+ }
216
+ else {
217
+ const getCallLogResult = await platformModule.getCallLog({ user, callLogId: callLog.thirdPartyLogId, contactId: callLog.contactId, authHeader });
218
+ returnMessage = getCallLogResult.returnMessage;
219
+ extraDataTracking = getCallLogResult.extraDataTracking;
220
+ logs.push({ sessionId: callLog.sessionId, matched: true, logId: callLog.thirdPartyLogId, logData: getCallLogResult.callLogInfo });
221
+ }
222
+ }
223
+ }
224
+ else {
225
+ const callLogs = await CallLogModel.findAll({
226
+ where: {
227
+ sessionId: {
228
+ [Op.in]: sessionIdsArray
229
+ }
230
+ }
231
+ });
232
+ for (const sId of sessionIdsArray) {
233
+ const callLog = callLogs.find(c => c.sessionId === sId);
234
+ if (!callLog) {
235
+ logs.push({ sessionId: sId, matched: false });
236
+ }
237
+ else {
238
+ logs.push({ sessionId: callLog.sessionId, matched: true, logId: callLog.thirdPartyLogId });
239
+ }
240
+ }
241
+ }
242
+ return { successful: true, logs, returnMessage, extraDataTracking };
243
+ }
244
+ catch (e) {
245
+ console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
246
+ if (e.response?.status === 429) {
247
+ return {
248
+ successful: false,
249
+ returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
250
+ extraDataTracking: {
251
+ statusCode: e.response?.status,
252
+ }
253
+ };
254
+ }
255
+ else if (e.response?.status >= 400 && e.response?.status < 410) {
256
+ return {
257
+ successful: false,
258
+ returnMessage: errorMessage.authorizationErrorMessage({ platform }),
259
+ extraDataTracking: {
260
+ statusCode: e.response?.status,
261
+ }
262
+ };
263
+ }
264
+ return {
265
+ successful: false,
266
+ returnMessage:
267
+ {
268
+ message: `Error getting call log`,
269
+ messageType: 'warning',
270
+ details: [
271
+ {
272
+ title: 'Details',
273
+ items: [
274
+ {
275
+ id: '1',
276
+ type: 'text',
277
+ text: `Please check if your account has permission to CREATE logs.`
278
+ }
279
+ ]
280
+ }
281
+ ],
282
+ ttl: 5000
283
+ },
284
+ extraDataTracking: {
285
+ statusCode: e.response?.status,
286
+ }
287
+ };
288
+ }
289
+ }
290
+
291
+ async function updateCallLog({ platform, userId, incomingData, isFromSSCL }) {
292
+ try {
293
+ const existingCallLog = await CallLogModel.findOne({
294
+ where: {
295
+ sessionId: incomingData.sessionId
296
+ }
297
+ });
298
+ if (existingCallLog) {
299
+ const platformModule = adapterRegistry.getAdapter(platform);
300
+ let user = await UserModel.findByPk(userId);
301
+ if (!user || !user.accessToken) {
302
+ return { successful: false, message: `Contact not found` };
303
+ }
304
+ const authType = platformModule.getAuthType();
305
+ let authHeader = '';
306
+ switch (authType) {
307
+ case 'oauth':
308
+ const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
309
+ user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
310
+ authHeader = `Bearer ${user.accessToken}`;
311
+ break;
312
+ case 'apiKey':
313
+ const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
314
+ authHeader = `Basic ${basicAuth}`;
315
+ break;
316
+ }
317
+
318
+ // Fetch existing call log details once to avoid duplicate API calls
319
+ let existingCallLogDetails = null; // Compose updated call log details centrally
320
+ const logFormat = getLogFormatType(platform);
321
+ let composedLogDetails = '';
322
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT || logFormat === LOG_DETAILS_FORMAT_TYPE.HTML || logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
323
+ let existingBody = '';
324
+ try {
325
+ const getLogResult = await platformModule.getCallLog({
326
+ user,
327
+ callLogId: existingCallLog.thirdPartyLogId,
328
+ authHeader
329
+ });
330
+ existingCallLogDetails = getLogResult?.callLogInfo?.fullLogResponse;
331
+ // Extract existing body from the platform-specific response
332
+ if (getLogResult.callLogInfo?.fullBody) {
333
+ existingBody = getLogResult.callLogInfo.fullBody;
334
+ } else if (getLogResult.callLogInfo?.note) {
335
+ existingBody = getLogResult.callLogInfo.note;
336
+ }
337
+ } catch (error) {
338
+ console.log('Error getting existing log details, proceeding with empty body', error);
339
+ }
340
+ composedLogDetails = await composeCallLog({
341
+ logFormat,
342
+ existingBody,
343
+ callLog: {
344
+ sessionId: existingCallLog.sessionId,
345
+ startTime: incomingData.startTime,
346
+ duration: incomingData.duration,
347
+ result: incomingData.result,
348
+ direction: incomingData.direction,
349
+ from: incomingData.from,
350
+ to: incomingData.to
351
+ },
352
+ contactInfo: null, // Not needed for updates
353
+ user,
354
+ note: incomingData.note,
355
+ aiNote: incomingData.aiNote,
356
+ transcript: incomingData.transcript,
357
+ recordingLink: incomingData.recordingLink,
358
+ subject: incomingData.subject,
359
+ startTime: incomingData.startTime,
360
+ duration: incomingData.duration,
361
+ result: incomingData.result,
362
+ });
363
+ }
364
+
365
+ const { updatedNote, returnMessage, extraDataTracking } = await platformModule.updateCallLog({
366
+ user,
367
+ existingCallLog,
368
+ authHeader,
369
+ recordingLink: incomingData.recordingLink,
370
+ recordingDownloadLink: incomingData.recordingDownloadLink,
371
+ subject: incomingData.subject,
372
+ note: incomingData.note,
373
+ startTime: incomingData.startTime,
374
+ duration: incomingData.duration,
375
+ result: incomingData.result,
376
+ aiNote: incomingData.aiNote,
377
+ transcript: incomingData.transcript,
378
+ additionalSubmission: incomingData.additionalSubmission,
379
+ composedLogDetails,
380
+ existingCallLogDetails, // Pass the fetched details to avoid duplicate API calls
381
+ isFromSSCL
382
+ });
383
+ return { successful: true, logId: existingCallLog.thirdPartyLogId, updatedNote, returnMessage, extraDataTracking };
384
+ }
385
+ return { successful: false };
386
+ } catch (e) {
387
+ console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
388
+ if (e.response?.status === 429) {
389
+ return {
390
+ successful: false,
391
+ returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
392
+ extraDataTracking: {
393
+ statusCode: e.response?.status,
394
+ }
395
+ };
396
+ }
397
+ else if (e.response?.status >= 400 && e.response?.status < 410) {
398
+ return {
399
+ successful: false,
400
+ returnMessage: errorMessage.authorizationErrorMessage({ platform }),
401
+ extraDataTracking: {
402
+ statusCode: e.response?.status,
403
+ }
404
+ };
405
+ }
406
+ return {
407
+ successful: false,
408
+ returnMessage:
409
+ {
410
+ message: `Error updating call log`,
411
+ messageType: 'warning',
412
+ details: [
413
+ {
414
+ title: 'Details',
415
+ items: [
416
+ {
417
+ id: '1',
418
+ type: 'text',
419
+ text: `Please check if the log entity still exist on ${platform} and your account has permission to EDIT logs.`
420
+ }
421
+ ]
422
+ }
423
+ ],
424
+ ttl: 5000
425
+ },
426
+ extraDataTracking: {
427
+ statusCode: e.response?.status,
428
+ }
429
+ };
430
+ }
431
+ }
432
+
433
+ async function createMessageLog({ platform, userId, incomingData }) {
434
+ try {
435
+ let returnMessage = null;
436
+ let extraDataTracking = {};;
437
+ if (incomingData.logInfo.messages.length === 0) {
438
+ return {
439
+ successful: false,
440
+ returnMessage:
441
+ {
442
+ message: 'No message to log.',
443
+ messageType: 'warning',
444
+ ttl: 3000
445
+ }
446
+ }
447
+ }
448
+ const platformModule = adapterRegistry.getAdapter(platform);
449
+ const contactNumber = incomingData.logInfo.correspondents[0].phoneNumber;
450
+ const additionalSubmission = incomingData.additionalSubmission;
451
+ let user = await UserModel.findByPk(userId);
452
+ if (!user || !user.accessToken) {
453
+ return {
454
+ successful: false,
455
+ returnMessage:
456
+ {
457
+ message: `Contact not found`,
458
+ messageType: 'warning',
459
+ ttl: 5000
460
+ }
461
+ };
462
+ }
463
+ const authType = platformModule.getAuthType();
464
+ let authHeader = '';
465
+ switch (authType) {
466
+ case 'oauth':
467
+ const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
468
+ user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
469
+ authHeader = `Bearer ${user.accessToken}`;
470
+ break;
471
+ case 'apiKey':
472
+ const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
473
+ authHeader = `Basic ${basicAuth}`;
474
+ break;
475
+ }
476
+ const contactId = incomingData.contactId;
477
+ if (!contactId) {
478
+ return {
479
+ successful: false,
480
+ returnMessage:
481
+ {
482
+ message: `Contact not found for number ${contactNumber}`,
483
+ messageType: 'warning',
484
+ ttl: 5000
485
+ }
486
+ };
487
+ }
488
+ const contactInfo = {
489
+ id: contactId,
490
+ phoneNumber: contactNumber,
491
+ type: incomingData.contactType ?? "",
492
+ name: incomingData.contactName ?? ""
493
+ };
494
+ const messageIds = incomingData.logInfo.messages.map(m => { return { id: m.id.toString() }; });
495
+ const existingMessages = await MessageLogModel.findAll({
496
+ where: {
497
+ [Op.or]: messageIds
498
+ }
499
+ });
500
+ const existingIds = existingMessages.map(m => m.id);
501
+ const logIds = [];
502
+ // reverse the order of messages to log the oldest message first
503
+ const reversedMessages = incomingData.logInfo.messages.reverse();
504
+ for (const message of reversedMessages) {
505
+ if (existingIds.includes(message.id.toString())) {
506
+ continue;
507
+ }
508
+ let recordingLink = null;
509
+ if (message.attachments && message.attachments.some(a => a.type === 'AudioRecording')) {
510
+ recordingLink = message.attachments.find(a => a.type === 'AudioRecording').link;
511
+ }
512
+ let faxDocLink = null;
513
+ let faxDownloadLink = null;
514
+ if (message.attachments && message.attachments.some(a => a.type === 'RenderedDocument')) {
515
+ faxDocLink = message.attachments.find(a => a.type === 'RenderedDocument').link;
516
+ faxDownloadLink = message.attachments.find(a => a.type === 'RenderedDocument').uri + `?access_token=${incomingData.logInfo.rcAccessToken}`
517
+ }
518
+ const existingSameDateMessageLog = await MessageLogModel.findOne({
519
+ where: {
520
+ conversationLogId: incomingData.logInfo.conversationLogId
521
+ }
522
+ });
523
+ let crmLogId = ''
524
+ if (existingSameDateMessageLog) {
525
+ const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission });
526
+ crmLogId = existingSameDateMessageLog.thirdPartyLogId;
527
+ returnMessage = updateMessageResult?.returnMessage;
528
+ }
529
+ else {
530
+ const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink });
531
+ crmLogId = createMessageLogResult.logId;
532
+ returnMessage = createMessageLogResult?.returnMessage;
533
+ extraDataTracking = createMessageLogResult.extraDataTracking;
534
+ }
535
+ if (crmLogId) {
536
+ const createdMessageLog =
537
+ await MessageLogModel.create({
538
+ id: message.id.toString(),
539
+ platform,
540
+ conversationId: incomingData.logInfo.conversationId,
541
+ thirdPartyLogId: crmLogId,
542
+ userId,
543
+ conversationLogId: incomingData.logInfo.conversationLogId
544
+ });
545
+ logIds.push(createdMessageLog.id);
546
+ }
547
+ }
548
+ return { successful: true, logIds, returnMessage, extraDataTracking };
549
+ }
550
+ catch (e) {
551
+ console.log(`platform: ${platform} \n${e.stack}`);
552
+ if (e.response?.status === 429) {
553
+ return {
554
+ successful: false,
555
+ returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
556
+ extraDataTracking: {
557
+ statusCode: e.response?.status,
558
+ }
559
+ };
560
+ }
561
+ else if (e.response?.status >= 400 && e.response?.status < 410) {
562
+ return {
563
+ successful: false,
564
+ returnMessage: errorMessage.authorizationErrorMessage({ platform }),
565
+ extraDataTracking: {
566
+ statusCode: e.response?.status,
567
+ }
568
+ };
569
+ }
570
+ return {
571
+ successful: false,
572
+ returnMessage:
573
+ {
574
+ message: `Error creating message log`,
575
+ messageType: 'warning',
576
+ details: [
577
+ {
578
+ title: 'Details',
579
+ items: [
580
+ {
581
+ id: '1',
582
+ type: 'text',
583
+ text: `Please check if your account has permission to CREATE logs.`
584
+ }
585
+ ]
586
+ }
587
+ ],
588
+ ttl: 5000
589
+ },
590
+ extraDataTracking: {
591
+ statusCode: e.response?.status,
592
+ }
593
+ };
594
+ }
595
+ }
596
+
597
+ async function saveNoteCache({ sessionId, note }) {
598
+ try {
599
+ const now = moment();
600
+ const noteCache = await NoteCache.create({ sessionId, note, ttl: now.unix() + 3600 });
601
+ return { successful: true, returnMessage: 'Note cache saved' };
602
+ } catch (e) {
603
+ console.error(`Error saving note cache: ${e.stack}`);
604
+ return { successful: false, returnMessage: 'Error saving note cache' };
605
+ }
606
+ }
607
+
608
+ exports.createCallLog = createCallLog;
609
+ exports.updateCallLog = updateCallLog;
610
+ exports.createMessageLog = createMessageLog;
611
+ exports.getCallLog = getCallLog;
612
+ exports.saveNoteCache = saveNoteCache;