@app-connect/core 1.7.5 → 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 +29 -17
- package/handlers/calldown.js +33 -0
- package/handlers/contact.js +20 -2
- package/index.js +348 -156
- package/lib/debugTracer.js +159 -0
- package/lib/oauth.js +28 -18
- package/lib/ringcentral.js +2 -2
- package/lib/s3ErrorLogReport.js +66 -0
- package/package.json +7 -5
- package/releaseNotes.json +44 -0
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
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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);
|
package/handlers/calldown.js
CHANGED
|
@@ -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
|
|
package/handlers/contact.js
CHANGED
|
@@ -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
|
-
|
|
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,
|