@app-connect/core 1.7.4 → 1.7.8

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/admin.js CHANGED
@@ -5,6 +5,8 @@ const oauth = require('../lib/oauth');
5
5
  const { RingCentral } = require('../lib/ringcentral');
6
6
  const { Connector } = require('../models/dynamo/connectorSchema');
7
7
 
8
+ const CALL_AGGREGATION_GROUPS = ["Company", "CompanyNumbers", "Users", "Queues", "IVRs", "IVAs", "SharedLines", "UserGroups", "Sites", "Departments"]
9
+
8
10
  async function validateAdminRole({ rcAccessToken }) {
9
11
  const rcExtensionResponse = await axios.get(
10
12
  'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
@@ -72,7 +74,7 @@ async function updateServerLoggingSettings({ user, additionalFieldValues }) {
72
74
  return {};
73
75
  }
74
76
 
75
- async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo }) {
77
+ async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo, groupBy }) {
76
78
  try {
77
79
  if (!process.env.RINGCENTRAL_SERVER || !process.env.RINGCENTRAL_CLIENT_ID || !process.env.RINGCENTRAL_CLIENT_SECRET) {
78
80
  return {
@@ -99,29 +101,39 @@ async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo }) {
99
101
  token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
100
102
  timezone,
101
103
  timeFrom,
102
- timeTo
104
+ timeTo,
105
+ groupBy: groupBy == 'undefined' ? CALL_AGGREGATION_GROUPS[0] : groupBy
103
106
  });
104
- var dataCounter = callsAggregationData.data.records[0].counters;
105
- var inboundCallCount = dataCounter.callsByDirection.values.inbound;
106
- var outboundCallCount = dataCounter.callsByDirection.values.outbound;
107
- var answeredCallCount = dataCounter.callsByResponse.values.answered;
108
- // keep 2 decimal places
109
- var answeredCallPercentage = inboundCallCount === 0 ? '0%' : `${((answeredCallCount / inboundCallCount) * 100).toFixed(2)}%`;
110
-
111
- var dataTimer = callsAggregationData.data.records[0].timers;
112
- // keep 2 decimal places
113
- var totalTalkTime = (dataTimer.allCalls.values / 60).toFixed(2);
114
- // keep 2 decimal places
115
- var averageTalkTime = (totalTalkTime / (inboundCallCount + outboundCallCount)).toFixed(2);
116
- return {
117
- callLogStats: {
107
+ var callLogStats = [];
108
+ var itemKeys = [];
109
+ for (const record of callsAggregationData.data.records) {
110
+ if(!record?.info?.name){
111
+ continue;
112
+ }
113
+ itemKeys.push(record.info.name);
114
+ var dataCounter = record.counters;
115
+ var inboundCallCount = dataCounter.callsByDirection.values.inbound;
116
+ var outboundCallCount = dataCounter.callsByDirection.values.outbound;
117
+ var answeredCallCount = dataCounter.callsByResponse.values.answered;
118
+ // keep 2 decimal places
119
+ var answeredCallPercentage = inboundCallCount === 0 ? '0%' : `${((answeredCallCount / inboundCallCount) * 100).toFixed(2)}%`;
120
+ var totalTalkTime = Number(record.timers.allCalls.values) === 0 ? 0 : Number(record.timers.allCalls.values).toFixed(2);
121
+ var averageTalkTime = Number(totalTalkTime) === 0 ? 0 : (Number(totalTalkTime) / (inboundCallCount + outboundCallCount)).toFixed(2);
122
+ callLogStats.push({
123
+ name: record.info.name,
118
124
  inboundCallCount,
119
125
  outboundCallCount,
120
126
  answeredCallCount,
121
127
  answeredCallPercentage,
122
128
  totalTalkTime,
123
129
  averageTalkTime
124
- }
130
+ });
131
+ }
132
+ return {
133
+ callLogStats,
134
+ itemKeys,
135
+ groupedBy: callsAggregationData.data.groupedBy,
136
+ groupKeys: CALL_AGGREGATION_GROUPS
125
137
  };
126
138
  } catch (error) {
127
139
  console.error(error);
@@ -53,8 +53,41 @@ async function markCalled({ jwtToken, id, lastCallAt }) {
53
53
  return { successful: true };
54
54
  }
55
55
 
56
+ async function update({ jwtToken, id, updateData }) {
57
+ const unAuthData = jwt.decodeJwt(jwtToken);
58
+ if (!unAuthData?.id) throw new Error('Unauthorized');
59
+
60
+ // Prepare the update object with only valid fields
61
+ const allowedFields = ['contactId', 'contactType', 'contactName', 'phoneNumber', 'status', 'scheduledAt', 'lastCallAt', 'note'];
62
+ const updateObject = {};
63
+
64
+ // Filter and prepare update data
65
+ Object.keys(updateData).forEach(key => {
66
+ if (allowedFields.includes(key)) {
67
+ let value = updateData[key];
68
+
69
+ // Handle date fields
70
+ if ((key === 'scheduledAt' || key === 'lastCallAt') && value) {
71
+ value = new Date(value);
72
+ }
73
+
74
+ updateObject[key] = value;
75
+ }
76
+ });
77
+
78
+ // If no valid fields to update, throw error
79
+ if (Object.keys(updateObject).length === 0) {
80
+ throw new Error('No valid fields to update');
81
+ }
82
+
83
+ const [affected] = await CallDownListModel.update(updateObject, { where: { id, userId: unAuthData.id } });
84
+ if (!affected) throw new Error('Not found');
85
+ return { successful: true };
86
+ }
87
+
56
88
  exports.schedule = schedule;
57
89
  exports.list = list;
58
90
  exports.remove = remove;
59
91
  exports.markCalled = markCalled;
92
+ exports.update = update;
60
93
 
@@ -3,8 +3,11 @@ const { UserModel } = require('../models/userModel');
3
3
  const errorMessage = require('../lib/generalErrorMessage');
4
4
  const connectorRegistry = require('../connector/registry');
5
5
  const { Connector } = require('../models/dynamo/connectorSchema');
6
+ const { DebugTracer } = require('../lib/debugTracer');
6
7
 
7
- async function findContact({ platform, userId, phoneNumber, overridingFormat, isExtension }) {
8
+ async function findContact({ platform, userId, phoneNumber, overridingFormat, isExtension, tracer }) {
9
+ tracer?.trace('handler.findContact:entered', { platform, userId, phoneNumber });
10
+
8
11
  try {
9
12
  let user = await UserModel.findOne({
10
13
  where: {
@@ -12,7 +15,10 @@ async function findContact({ platform, userId, phoneNumber, overridingFormat, is
12
15
  platform
13
16
  }
14
17
  });
18
+ tracer?.trace('handler.findContact:userFound', { user });
19
+
15
20
  if (!user || !user.accessToken) {
21
+ tracer?.trace('handler.findContact:noUser', { userId });
16
22
  return {
17
23
  successful: false,
18
24
  returnMessage: {
@@ -26,26 +32,36 @@ async function findContact({ platform, userId, phoneNumber, overridingFormat, is
26
32
  let proxyConfig = null;
27
33
  if (proxyId) {
28
34
  proxyConfig = await Connector.getProxyConfig(proxyId);
35
+ tracer?.trace('handler.findContact:proxyConfig', { proxyConfig });
29
36
  }
30
37
  const platformModule = connectorRegistry.getConnector(platform);
31
38
  const authType = await platformModule.getAuthType({ proxyId, proxyConfig });
39
+ tracer?.trace('handler.findContact:authType', { authType });
40
+
32
41
  let authHeader = '';
33
42
  switch (authType) {
34
43
  case 'oauth':
35
44
  const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
36
45
  user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
37
46
  authHeader = `Bearer ${user.accessToken}`;
47
+ tracer?.trace('handler.findContact:oauthAuth', { authHeader });
38
48
  break;
39
49
  case 'apiKey':
40
50
  const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
41
51
  authHeader = `Basic ${basicAuth}`;
52
+ tracer?.trace('handler.findContact:apiKeyAuth', {});
42
53
  break;
43
54
  }
44
- const { successful, matchedContactInfo, returnMessage, extraDataTracking } = await platformModule.findContact({ user, authHeader, phoneNumber, overridingFormat, isExtension, proxyConfig });
55
+
56
+ const { successful, matchedContactInfo, returnMessage, extraDataTracking } = await platformModule.findContact({ user, authHeader, phoneNumber, overridingFormat, isExtension, proxyConfig, tracer });
57
+ tracer?.trace('handler.findContact:platformFindResult', { successful, matchedContactInfo });
58
+
45
59
  if (matchedContactInfo != null && matchedContactInfo?.filter(c => !c.isNewContact)?.length > 0) {
60
+ tracer?.trace('handler.findContact:contactsFound', { count: matchedContactInfo.length });
46
61
  return { successful, returnMessage, contact: matchedContactInfo, extraDataTracking };
47
62
  }
48
63
  else {
64
+ tracer?.trace('handler.findContact:noContactsMatched', { matchedContactInfo });
49
65
  if (returnMessage) {
50
66
  return {
51
67
  successful,
@@ -78,6 +94,8 @@ async function findContact({ platform, userId, phoneNumber, overridingFormat, is
78
94
  }
79
95
  } catch (e) {
80
96
  console.error(`platform: ${platform} \n${e.stack} \n${JSON.stringify(e.response?.data)}`);
97
+ tracer?.traceError('handler.findContact:error', e, { platform, statusCode: e.response?.status });
98
+
81
99
  if (e.response?.status === 429) {
82
100
  return {
83
101
  successful: false,
package/handlers/log.js CHANGED
@@ -359,6 +359,7 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
359
359
  const getLogResult = await platformModule.getCallLog({
360
360
  user,
361
361
  callLogId: existingCallLog.thirdPartyLogId,
362
+ contactId: existingCallLog.contactId,
362
363
  authHeader,
363
364
  proxyConfig,
364
365
  });
@@ -570,6 +571,16 @@ async function createMessageLog({ platform, userId, incomingData }) {
570
571
  faxDownloadLink = message.attachments.find(a => a.type === 'RenderedDocument').uri + `?access_token=${incomingData.logInfo.rcAccessToken}`
571
572
  }
572
573
  let imageLink = null;
574
+ let imageDownloadLink = null;
575
+ let imageContentType = null;
576
+ if (message.attachments && message.attachments.some(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'))) {
577
+ const imageAttachment = message.attachments.find(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'));
578
+ if (imageAttachment) {
579
+ imageLink = getMediaReaderLinkByPlatformMediaLink(imageAttachment?.uri);
580
+ imageDownloadLink = imageAttachment?.uri + `?access_token=${incomingData.logInfo.rcAccessToken}`;
581
+ imageContentType = imageAttachment?.contentType;
582
+ }
583
+ }
573
584
  let videoLink = null;
574
585
  if (message.attachments && message.attachments.some(a => a.type === 'MmsAttachment')) {
575
586
  const imageAttachment = message.attachments.find(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'));
@@ -593,7 +604,7 @@ async function createMessageLog({ platform, userId, incomingData }) {
593
604
  returnMessage = updateMessageResult?.returnMessage;
594
605
  }
595
606
  else {
596
- const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink, imageLink, videoLink, proxyConfig });
607
+ const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink, imageLink, imageDownloadLink, imageContentType, videoLink, proxyConfig });
597
608
  crmLogId = createMessageLogResult.logId;
598
609
  returnMessage = createMessageLogResult?.returnMessage;
599
610
  extraDataTracking = createMessageLogResult.extraDataTracking;