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