@app-connect/core 1.6.9 → 1.7.0-beta.2
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 +136 -0
- package/handlers/auth.js +22 -1
- package/handlers/calldown.js +60 -0
- package/index.js +274 -2
- package/lib/ringcentral.js +275 -0
- package/lib/util.js +15 -0
- package/models/adminConfigModel.js +11 -2
- package/models/callDownListModel.js +35 -0
- package/models/userModel.js +1 -0
- package/package.json +1 -1
- package/releaseNotes.json +0 -24
package/handlers/admin.js
CHANGED
|
@@ -2,6 +2,7 @@ const axios = require('axios');
|
|
|
2
2
|
const { AdminConfigModel } = require('../models/adminConfigModel');
|
|
3
3
|
const adapterRegistry = require('../adapter/registry');
|
|
4
4
|
const oauth = require('../lib/oauth');
|
|
5
|
+
const { RingCentral } = require('../lib/ringcentral');
|
|
5
6
|
|
|
6
7
|
async function validateAdminRole({ rcAccessToken }) {
|
|
7
8
|
const rcExtensionResponse = await axios.get(
|
|
@@ -36,6 +37,21 @@ async function getAdminSettings({ hashedRcAccountId }) {
|
|
|
36
37
|
return existingAdminConfig;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
async function updateAdminRcTokens({ hashedRcAccountId, adminAccessToken, adminRefreshToken, adminTokenExpiry }) {
|
|
41
|
+
const existingAdminConfig = await AdminConfigModel.findByPk(hashedRcAccountId);
|
|
42
|
+
if (existingAdminConfig) {
|
|
43
|
+
await existingAdminConfig.update({ adminAccessToken, adminRefreshToken, adminTokenExpiry });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await AdminConfigModel.create({
|
|
47
|
+
id: hashedRcAccountId,
|
|
48
|
+
adminAccessToken,
|
|
49
|
+
adminRefreshToken,
|
|
50
|
+
adminTokenExpiry
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
async function getServerLoggingSettings({ user }) {
|
|
40
56
|
const platformModule = adapterRegistry.getAdapter(user.platform);
|
|
41
57
|
if (platformModule.getServerLoggingSettings) {
|
|
@@ -55,6 +71,123 @@ async function updateServerLoggingSettings({ user, additionalFieldValues }) {
|
|
|
55
71
|
return {};
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo }) {
|
|
75
|
+
try {
|
|
76
|
+
const rcSDK = new RingCentral({
|
|
77
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
78
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
79
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
80
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
81
|
+
});
|
|
82
|
+
let adminConfig = await AdminConfigModel.findByPk(rcAccountId);
|
|
83
|
+
const isTokenExpired = adminConfig.adminTokenExpiry < new Date();
|
|
84
|
+
if (isTokenExpired) {
|
|
85
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.refreshToken({
|
|
86
|
+
refresh_token: adminConfig.adminRefreshToken,
|
|
87
|
+
expires_in: adminConfig.adminTokenExpiry,
|
|
88
|
+
refresh_token_expires_in: adminConfig.adminTokenExpiry
|
|
89
|
+
});
|
|
90
|
+
adminConfig = await AdminConfigModel.update({ adminAccessToken: access_token, adminRefreshToken: refresh_token, adminTokenExpiry: expire_time }, { where: { id: rcAccountId } });
|
|
91
|
+
}
|
|
92
|
+
const callsAggregationData = await rcSDK.getCallsAggregationData({
|
|
93
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
94
|
+
timezone,
|
|
95
|
+
timeFrom,
|
|
96
|
+
timeTo
|
|
97
|
+
});
|
|
98
|
+
var dataCounter = callsAggregationData.data.records[0].counters;
|
|
99
|
+
var inboundCallCount = dataCounter.callsByDirection.values.inbound;
|
|
100
|
+
var outboundCallCount = dataCounter.callsByDirection.values.outbound;
|
|
101
|
+
var answeredCallCount = dataCounter.callsByResponse.values.answered;
|
|
102
|
+
// keep 2 decimal places
|
|
103
|
+
var answeredCallPercentage = inboundCallCount === 0 ? '0%' : `${((answeredCallCount / inboundCallCount) * 100).toFixed(2)}%`;
|
|
104
|
+
|
|
105
|
+
var dataTimer = callsAggregationData.data.records[0].timers;
|
|
106
|
+
// keep 2 decimal places
|
|
107
|
+
var totalTalkTime = (dataTimer.allCalls.values / 60).toFixed(2);
|
|
108
|
+
// keep 2 decimal places
|
|
109
|
+
var averageTalkTime = (totalTalkTime / (inboundCallCount + outboundCallCount)).toFixed(2);
|
|
110
|
+
return {
|
|
111
|
+
callLogStats: {
|
|
112
|
+
inboundCallCount,
|
|
113
|
+
outboundCallCount,
|
|
114
|
+
answeredCallCount,
|
|
115
|
+
answeredCallPercentage,
|
|
116
|
+
totalTalkTime,
|
|
117
|
+
averageTalkTime
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(error);
|
|
122
|
+
return {
|
|
123
|
+
callLogStats: {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function getUserReport({ rcAccountId, rcExtensionId, timezone, timeFrom, timeTo }) {
|
|
129
|
+
try {
|
|
130
|
+
const rcSDK = new RingCentral({
|
|
131
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
132
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
133
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
134
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
135
|
+
});
|
|
136
|
+
let adminConfig = await AdminConfigModel.findByPk(rcAccountId);
|
|
137
|
+
const isTokenExpired = adminConfig.adminTokenExpiry < new Date();
|
|
138
|
+
if (isTokenExpired) {
|
|
139
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.refreshToken({
|
|
140
|
+
refresh_token: adminConfig.adminRefreshToken,
|
|
141
|
+
expires_in: adminConfig.adminTokenExpiry,
|
|
142
|
+
refresh_token_expires_in: adminConfig.adminTokenExpiry
|
|
143
|
+
});
|
|
144
|
+
adminConfig = await AdminConfigModel.update({ adminAccessToken: access_token, adminRefreshToken: refresh_token, adminTokenExpiry: expire_time }, { where: { id: rcAccountId } });
|
|
145
|
+
}
|
|
146
|
+
const callLogData = await rcSDK.getCallLogData({
|
|
147
|
+
extensionId: rcExtensionId,
|
|
148
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
149
|
+
timezone,
|
|
150
|
+
timeFrom,
|
|
151
|
+
timeTo
|
|
152
|
+
});
|
|
153
|
+
// phone activity
|
|
154
|
+
const inboundCallCount = callLogData.records.filter(call => call.direction === 'Inbound').length;
|
|
155
|
+
const outboundCallCount = callLogData.records.filter(call => call.direction === 'Outbound').length;
|
|
156
|
+
const answeredCallCount = callLogData.records.filter(call => call.direction === 'Inbound' && (call.result === 'Call connected' || call.result === 'Accepted' || call.result === 'Answered Not Accepted')).length;
|
|
157
|
+
const answeredCallPercentage = answeredCallCount === 0 ? '0%' : `${((answeredCallCount / (inboundCallCount || 1)) * 100).toFixed(2)}%`;
|
|
158
|
+
// phone engagement
|
|
159
|
+
const totalTalkTime = Math.round(callLogData.records.reduce((acc, call) => acc + (call.duration || 0), 0) / 60) || 0;
|
|
160
|
+
const averageTalkTime = Math.round(totalTalkTime / (inboundCallCount + outboundCallCount)) || 0;
|
|
161
|
+
const smsLogData = await rcSDK.getSMSData({
|
|
162
|
+
extensionId: rcExtensionId,
|
|
163
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
164
|
+
timezone,
|
|
165
|
+
timeFrom,
|
|
166
|
+
timeTo
|
|
167
|
+
});
|
|
168
|
+
const smsSentCount = smsLogData.records.filter(sms => sms.direction === 'Outbound').length;
|
|
169
|
+
const smsReceivedCount = smsLogData.records.filter(sms => sms.direction === 'Inbound').length;
|
|
170
|
+
const reportStats = {
|
|
171
|
+
callLogStats: {
|
|
172
|
+
inboundCallCount,
|
|
173
|
+
outboundCallCount,
|
|
174
|
+
answeredCallCount,
|
|
175
|
+
answeredCallPercentage,
|
|
176
|
+
totalTalkTime,
|
|
177
|
+
averageTalkTime
|
|
178
|
+
},
|
|
179
|
+
smsLogStats: {
|
|
180
|
+
smsSentCount,
|
|
181
|
+
smsReceivedCount
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return reportStats;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(error);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
58
191
|
async function getUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
59
192
|
const adminConfig = await getAdminSettings({ hashedRcAccountId });
|
|
60
193
|
const platformModule = adapterRegistry.getAdapter(user.platform);
|
|
@@ -188,6 +321,9 @@ async function getUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
|
188
321
|
exports.validateAdminRole = validateAdminRole;
|
|
189
322
|
exports.upsertAdminSettings = upsertAdminSettings;
|
|
190
323
|
exports.getAdminSettings = getAdminSettings;
|
|
324
|
+
exports.updateAdminRcTokens = updateAdminRcTokens;
|
|
191
325
|
exports.getServerLoggingSettings = getServerLoggingSettings;
|
|
192
326
|
exports.updateServerLoggingSettings = updateServerLoggingSettings;
|
|
327
|
+
exports.getAdminReport = getAdminReport;
|
|
328
|
+
exports.getUserReport = getUserReport;
|
|
193
329
|
exports.getUserMapping = getUserMapping;
|
package/handlers/auth.js
CHANGED
|
@@ -2,6 +2,8 @@ const oauth = require('../lib/oauth');
|
|
|
2
2
|
const { UserModel } = require('../models/userModel');
|
|
3
3
|
const adapterRegistry = require('../adapter/registry');
|
|
4
4
|
const Op = require('sequelize').Op;
|
|
5
|
+
const { RingCentral } = require('../lib/ringcentral');
|
|
6
|
+
const adminCore = require('./admin');
|
|
5
7
|
|
|
6
8
|
async function onOAuthCallback({ platform, hostname, tokenUrl, callbackUri, apiUrl, username, query }) {
|
|
7
9
|
const platformModule = adapterRegistry.getAdapter(platform);
|
|
@@ -92,6 +94,7 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
|
|
|
92
94
|
if (existingUser) {
|
|
93
95
|
await existingUser.update(
|
|
94
96
|
{
|
|
97
|
+
platform,
|
|
95
98
|
hostname,
|
|
96
99
|
timezoneName,
|
|
97
100
|
timezoneOffset,
|
|
@@ -204,7 +207,25 @@ async function authValidation({ platform, userId }) {
|
|
|
204
207
|
}
|
|
205
208
|
}
|
|
206
209
|
|
|
210
|
+
// Ringcentral
|
|
211
|
+
async function onRingcentralOAuthCallback({ code, rcAccountId }) {
|
|
212
|
+
const rcSDK = new RingCentral({
|
|
213
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
214
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
215
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
216
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
217
|
+
});
|
|
218
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.generateToken({ code });
|
|
219
|
+
await adminCore.updateAdminRcTokens({
|
|
220
|
+
hashedRcAccountId: rcAccountId,
|
|
221
|
+
adminAccessToken: access_token,
|
|
222
|
+
adminRefreshToken: refresh_token,
|
|
223
|
+
adminTokenExpiry: expire_time
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
207
227
|
exports.onOAuthCallback = onOAuthCallback;
|
|
208
228
|
exports.onApiKeyLogin = onApiKeyLogin;
|
|
209
229
|
exports.authValidation = authValidation;
|
|
210
|
-
exports.getLicenseStatus = getLicenseStatus;
|
|
230
|
+
exports.getLicenseStatus = getLicenseStatus;
|
|
231
|
+
exports.onRingcentralOAuthCallback = onRingcentralOAuthCallback;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { UserModel } = require('../models/userModel');
|
|
2
|
+
const { CallDownListModel } = require('../models/callDownListModel');
|
|
3
|
+
const { Op } = require('sequelize');
|
|
4
|
+
const jwt = require('../lib/jwt');
|
|
5
|
+
|
|
6
|
+
async function schedule({ jwtToken, rcAccessToken, body }) {
|
|
7
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
8
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
9
|
+
const user = await UserModel.findByPk(unAuthData.id);
|
|
10
|
+
if (!user) throw new Error('User not found');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const recordId = crypto.randomBytes(16).toString('hex');
|
|
13
|
+
const payload = {
|
|
14
|
+
id: recordId,
|
|
15
|
+
userId: user.id,
|
|
16
|
+
contactId: body.contactId?.toString?.() ?? body.contactId,
|
|
17
|
+
contactType: body.contactType ?? 'contact',
|
|
18
|
+
contactName: body.contactName ?? '',
|
|
19
|
+
phoneNumber: body.phoneNumber ?? '',
|
|
20
|
+
status: 'scheduled',
|
|
21
|
+
scheduledAt: body.scheduledAt ? new Date(body.scheduledAt) : null,
|
|
22
|
+
lastCallAt: null
|
|
23
|
+
};
|
|
24
|
+
await CallDownListModel.create(payload);
|
|
25
|
+
return { id: recordId };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function list({ jwtToken, status }) {
|
|
29
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
30
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
31
|
+
const statusParam = (status || 'All').toString().toLowerCase();
|
|
32
|
+
const whereClause = { userId: unAuthData.id };
|
|
33
|
+
if (statusParam === 'called') whereClause.status = 'called';
|
|
34
|
+
else if (['not called', 'not_called', 'notcalled'].includes(statusParam)) whereClause.status = { [Op.ne]: 'called' };
|
|
35
|
+
const items = await CallDownListModel.findAll({ where: whereClause, order: [["scheduledAt", "ASC"]] });
|
|
36
|
+
return { items };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function remove({ jwtToken, id }) {
|
|
40
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
41
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
42
|
+
const deleted = await CallDownListModel.destroy({ where: { id, userId: unAuthData.id } });
|
|
43
|
+
if (!deleted) throw new Error('Not found');
|
|
44
|
+
return { successful: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function markCalled({ jwtToken, id, lastCallAt }) {
|
|
48
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
49
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
50
|
+
const when = lastCallAt ? new Date(lastCallAt) : new Date();
|
|
51
|
+
const [affected] = await CallDownListModel.update({ status: 'called', lastCallAt: when }, { where: { id, userId: unAuthData.id } });
|
|
52
|
+
if (!affected) throw new Error('Not found');
|
|
53
|
+
return { successful: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.schedule = schedule;
|
|
57
|
+
exports.list = list;
|
|
58
|
+
exports.remove = remove;
|
|
59
|
+
exports.markCalled = markCalled;
|
|
60
|
+
|
package/index.js
CHANGED
|
@@ -4,6 +4,8 @@ const bodyParser = require('body-parser');
|
|
|
4
4
|
const dynamoose = require('dynamoose');
|
|
5
5
|
const axios = require('axios');
|
|
6
6
|
const { UserModel } = require('./models/userModel');
|
|
7
|
+
const { CallDownListModel } = require('./models/callDownListModel');
|
|
8
|
+
const { Op } = require('sequelize');
|
|
7
9
|
const { CallLogModel } = require('./models/callLogModel');
|
|
8
10
|
const { MessageLogModel } = require('./models/messageLogModel');
|
|
9
11
|
const { AdminConfigModel } = require('./models/adminConfigModel');
|
|
@@ -20,6 +22,7 @@ const releaseNotes = require('./releaseNotes.json');
|
|
|
20
22
|
const analytics = require('./lib/analytics');
|
|
21
23
|
const util = require('./lib/util');
|
|
22
24
|
const adapterRegistry = require('./adapter/registry');
|
|
25
|
+
const calldown = require('./handlers/calldown');
|
|
23
26
|
|
|
24
27
|
let packageJson = null;
|
|
25
28
|
try {
|
|
@@ -44,6 +47,7 @@ async function initDB() {
|
|
|
44
47
|
await MessageLogModel.sync();
|
|
45
48
|
await AdminConfigModel.sync();
|
|
46
49
|
await CacheModel.sync();
|
|
50
|
+
await CallDownListModel.sync();
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -1000,7 +1004,7 @@ function createCoreRouter() {
|
|
|
1000
1004
|
}
|
|
1001
1005
|
const { id: userId, platform } = decodedToken;
|
|
1002
1006
|
platformName = platform;
|
|
1003
|
-
const { successful, logId, returnMessage, extraDataTracking } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL'});
|
|
1007
|
+
const { successful, logId, returnMessage, extraDataTracking } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1004
1008
|
if (extraDataTracking) {
|
|
1005
1009
|
extraData = extraDataTracking;
|
|
1006
1010
|
}
|
|
@@ -1199,6 +1203,174 @@ function createCoreRouter() {
|
|
|
1199
1203
|
});
|
|
1200
1204
|
});
|
|
1201
1205
|
|
|
1206
|
+
router.post('/calldown', async function (req, res) {
|
|
1207
|
+
const requestStartTime = new Date().getTime();
|
|
1208
|
+
let platformName = null;
|
|
1209
|
+
let success = false;
|
|
1210
|
+
let statusCode = 200;
|
|
1211
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1212
|
+
try {
|
|
1213
|
+
const jwtToken = req.query.jwtToken;
|
|
1214
|
+
if (!jwtToken) {
|
|
1215
|
+
res.status(400).send('Please go to Settings and authorize CRM platform');
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
|
|
1219
|
+
success = true;
|
|
1220
|
+
res.status(200).send({ successful: true, id });
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
console.log(`platform: ${platformName} \n${e.stack}`);
|
|
1223
|
+
statusCode = e.response?.status ?? 'unknown';
|
|
1224
|
+
res.status(400).send(e);
|
|
1225
|
+
success = false;
|
|
1226
|
+
}
|
|
1227
|
+
const requestEndTime = new Date().getTime();
|
|
1228
|
+
analytics.track({
|
|
1229
|
+
eventName: 'Schedule call down',
|
|
1230
|
+
interfaceName: 'scheduleCallDown',
|
|
1231
|
+
adapterName: platformName,
|
|
1232
|
+
accountId: hashedAccountId,
|
|
1233
|
+
extensionId: hashedExtensionId,
|
|
1234
|
+
success,
|
|
1235
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1236
|
+
userAgent,
|
|
1237
|
+
ip,
|
|
1238
|
+
author,
|
|
1239
|
+
extras: {
|
|
1240
|
+
statusCode
|
|
1241
|
+
},
|
|
1242
|
+
eventAddedVia
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
router.get('/calldown', async function (req, res) {
|
|
1248
|
+
const requestStartTime = new Date().getTime();
|
|
1249
|
+
let platformName = null;
|
|
1250
|
+
let success = false;
|
|
1251
|
+
let statusCode = 200;
|
|
1252
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1253
|
+
try {
|
|
1254
|
+
const jwtToken = req.query.jwtToken;
|
|
1255
|
+
if (!jwtToken) {
|
|
1256
|
+
res.status(400).send('Please go to Settings and authorize CRM platform');
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const { items } = await calldown.list({ jwtToken, status: req.query.status });
|
|
1260
|
+
success = true;
|
|
1261
|
+
res.status(200).send({ successful: true, items });
|
|
1262
|
+
} catch (e) {
|
|
1263
|
+
console.log(`platform: ${platformName} \n${e.stack}`);
|
|
1264
|
+
statusCode = e.response?.status ?? 'unknown';
|
|
1265
|
+
res.status(400).send(e);
|
|
1266
|
+
success = false;
|
|
1267
|
+
}
|
|
1268
|
+
const requestEndTime = new Date().getTime();
|
|
1269
|
+
analytics.track({
|
|
1270
|
+
eventName: 'Get call down list',
|
|
1271
|
+
interfaceName: 'getCallDownList',
|
|
1272
|
+
adapterName: platformName,
|
|
1273
|
+
accountId: hashedAccountId,
|
|
1274
|
+
extensionId: hashedExtensionId,
|
|
1275
|
+
success,
|
|
1276
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1277
|
+
userAgent,
|
|
1278
|
+
ip,
|
|
1279
|
+
author,
|
|
1280
|
+
extras: { statusCode },
|
|
1281
|
+
eventAddedVia
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
router.delete('/calldown/:id', async function (req, res) {
|
|
1287
|
+
const requestStartTime = new Date().getTime();
|
|
1288
|
+
let platformName = null;
|
|
1289
|
+
let success = false;
|
|
1290
|
+
let statusCode = 200;
|
|
1291
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1292
|
+
try {
|
|
1293
|
+
const jwtToken = req.query.jwtToken;
|
|
1294
|
+
const id = req.query.id;
|
|
1295
|
+
if (!jwtToken) {
|
|
1296
|
+
res.status(400).send('Please go to Settings and authorize CRM platform');
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const rid = req.params.id || id;
|
|
1300
|
+
if (!rid) {
|
|
1301
|
+
res.status(400).send('Missing id');
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
await calldown.remove({ jwtToken, id: rid });
|
|
1305
|
+
success = true;
|
|
1306
|
+
res.status(200).send({ successful: true });
|
|
1307
|
+
} catch (e) {
|
|
1308
|
+
console.log(`platform: ${platformName} \n${e.stack}`);
|
|
1309
|
+
statusCode = e.response?.status ?? 'unknown';
|
|
1310
|
+
res.status(400).send(e);
|
|
1311
|
+
success = false;
|
|
1312
|
+
}
|
|
1313
|
+
const requestEndTime = new Date().getTime();
|
|
1314
|
+
analytics.track({
|
|
1315
|
+
eventName: 'Delete call down item',
|
|
1316
|
+
interfaceName: 'deleteCallDownItem',
|
|
1317
|
+
adapterName: platformName,
|
|
1318
|
+
accountId: hashedAccountId,
|
|
1319
|
+
extensionId: hashedExtensionId,
|
|
1320
|
+
success,
|
|
1321
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1322
|
+
userAgent,
|
|
1323
|
+
ip,
|
|
1324
|
+
author,
|
|
1325
|
+
extras: { statusCode },
|
|
1326
|
+
eventAddedVia
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
router.patch('/calldown/:id', async function (req, res) {
|
|
1332
|
+
const requestStartTime = new Date().getTime();
|
|
1333
|
+
let platformName = null;
|
|
1334
|
+
let success = false;
|
|
1335
|
+
let statusCode = 200;
|
|
1336
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1337
|
+
try {
|
|
1338
|
+
const jwtToken = req.query.jwtToken;
|
|
1339
|
+
if (!jwtToken) {
|
|
1340
|
+
res.status(400).send('Please go to Settings and authorize CRM platform');
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const id = req.params.id || req.body?.id;
|
|
1344
|
+
if (!id) {
|
|
1345
|
+
res.status(400).send('Missing id');
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
await calldown.markCalled({ jwtToken, id, lastCallAt: req.body?.lastCallAt });
|
|
1349
|
+
success = true;
|
|
1350
|
+
res.status(200).send({ successful: true });
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
console.log(`platform: ${platformName} \n${e.stack}`);
|
|
1353
|
+
statusCode = e.response?.status ?? 'unknown';
|
|
1354
|
+
res.status(400).send(e);
|
|
1355
|
+
success = false;
|
|
1356
|
+
}
|
|
1357
|
+
const requestEndTime = new Date().getTime();
|
|
1358
|
+
analytics.track({
|
|
1359
|
+
eventName: 'Mark call down called',
|
|
1360
|
+
interfaceName: 'markCallDownCalled',
|
|
1361
|
+
adapterName: platformName,
|
|
1362
|
+
accountId: hashedAccountId,
|
|
1363
|
+
extensionId: hashedExtensionId,
|
|
1364
|
+
success,
|
|
1365
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1366
|
+
userAgent,
|
|
1367
|
+
ip,
|
|
1368
|
+
author,
|
|
1369
|
+
extras: { statusCode },
|
|
1370
|
+
eventAddedVia
|
|
1371
|
+
});
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1202
1374
|
router.get('/custom/contact/search', async function (req, res) {
|
|
1203
1375
|
const requestStartTime = new Date().getTime();
|
|
1204
1376
|
let platformName = null;
|
|
@@ -1243,8 +1415,108 @@ function createCoreRouter() {
|
|
|
1243
1415
|
statusCode
|
|
1244
1416
|
}
|
|
1245
1417
|
});
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
router.get('/ringcentral/admin/report', async function (req, res) {
|
|
1421
|
+
const requestStartTime = new Date().getTime();
|
|
1422
|
+
let platformName = null;
|
|
1423
|
+
let success = false;
|
|
1424
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1425
|
+
const jwtToken = req.query.jwtToken;
|
|
1426
|
+
try {
|
|
1427
|
+
if (jwtToken) {
|
|
1428
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
1429
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
1430
|
+
if (!user) {
|
|
1431
|
+
res.status(400).send('User not found');
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const report = await adminCore.getAdminReport({ rcAccountId: user.rcAccountId, timezone: req.query.timezone, timeFrom: req.query.timeFrom, timeTo: req.query.timeTo });
|
|
1435
|
+
res.status(200).send(report);
|
|
1436
|
+
success = true;
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
res.status(400).send('Invalid request');
|
|
1440
|
+
success = false;
|
|
1441
|
+
}
|
|
1442
|
+
catch (e) {
|
|
1443
|
+
console.log(`${e.stack}`);
|
|
1444
|
+
res.status(400).send(e);
|
|
1445
|
+
}
|
|
1446
|
+
const requestEndTime = new Date().getTime();
|
|
1447
|
+
analytics.track({
|
|
1448
|
+
eventName: 'Get admin report',
|
|
1449
|
+
interfaceName: 'getAdminReport',
|
|
1450
|
+
adapterName: platformName,
|
|
1451
|
+
accountId: hashedAccountId,
|
|
1452
|
+
extensionId: hashedExtensionId,
|
|
1453
|
+
success,
|
|
1454
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1455
|
+
userAgent,
|
|
1456
|
+
ip,
|
|
1457
|
+
author,
|
|
1458
|
+
eventAddedVia
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
router.get('/ringcentral/admin/userReport', async function (req, res) {
|
|
1463
|
+
const requestStartTime = new Date().getTime();
|
|
1464
|
+
let platformName = null;
|
|
1465
|
+
let success = false;
|
|
1466
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1467
|
+
const jwtToken = req.query.jwtToken;
|
|
1468
|
+
try {
|
|
1469
|
+
if (jwtToken) {
|
|
1470
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
1471
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
1472
|
+
if (!user) {
|
|
1473
|
+
res.status(400).send('User not found');
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const report = await adminCore.getUserReport({ rcAccountId: user.rcAccountId, rcExtensionId: req.query.rcExtensionId, timezone: req.query.timezone, timeFrom: req.query.timeFrom, timeTo: req.query.timeTo });
|
|
1477
|
+
res.status(200).send(report);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
res.status(400).send('Invalid request');
|
|
1481
|
+
success = false;
|
|
1482
|
+
}
|
|
1483
|
+
catch (e) {
|
|
1484
|
+
console.log(`${e.stack}`);
|
|
1485
|
+
res.status(400).send(e);
|
|
1486
|
+
}
|
|
1487
|
+
const requestEndTime = new Date().getTime();
|
|
1488
|
+
analytics.track({
|
|
1489
|
+
eventName: 'Get user report',
|
|
1490
|
+
interfaceName: 'getUserReport',
|
|
1491
|
+
adapterName: platformName,
|
|
1492
|
+
accountId: hashedAccountId,
|
|
1493
|
+
extensionId: hashedExtensionId,
|
|
1494
|
+
success,
|
|
1495
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1496
|
+
userAgent,
|
|
1497
|
+
ip,
|
|
1498
|
+
author,
|
|
1499
|
+
eventAddedVia
|
|
1500
|
+
});
|
|
1501
|
+
});
|
|
1246
1502
|
|
|
1503
|
+
router.get('/ringcentral/oauth/callback', async function (req, res) {
|
|
1504
|
+
const jwtToken = req.query.jwtToken;
|
|
1505
|
+
if (jwtToken) {
|
|
1506
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
1507
|
+
const { code } = req.query;
|
|
1508
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
1509
|
+
if (!user) {
|
|
1510
|
+
res.status(400).send('User not found');
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
await authCore.onRingcentralOAuthCallback({ code, rcAccountId: user.rcAccountId });
|
|
1514
|
+
res.status(200).send('OK');
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
res.status(400).send('Invalid request');
|
|
1247
1518
|
});
|
|
1519
|
+
|
|
1248
1520
|
if (process.env.IS_PROD === 'false') {
|
|
1249
1521
|
router.post('/registerMockUser', async function (req, res) {
|
|
1250
1522
|
const secretKey = req.query.secretKey;
|
|
@@ -1306,7 +1578,7 @@ function createCoreMiddleware() {
|
|
|
1306
1578
|
return [
|
|
1307
1579
|
bodyParser.json(),
|
|
1308
1580
|
cors({
|
|
1309
|
-
methods: ['GET', 'POST', 'PATCH', 'PUT']
|
|
1581
|
+
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
|
|
1310
1582
|
})
|
|
1311
1583
|
];
|
|
1312
1584
|
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
|
|
2
|
+
const fetch = require('node-fetch');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_RENEW_HANDICAP_MS = 60 * 1000; // 1 minute
|
|
5
|
+
|
|
6
|
+
function stringifyQuery(query) {
|
|
7
|
+
const queryParams = new URLSearchParams(query);
|
|
8
|
+
return queryParams.toString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const REFRESH_RENEW_HANDICAP_MS = 10 * 1000; // 10s
|
|
12
|
+
function isRefreshTokenValid(token, handicap = REFRESH_RENEW_HANDICAP_MS) {
|
|
13
|
+
const expireTime = token.refresh_token_expire_time;
|
|
14
|
+
return expireTime - handicap > Date.now();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isAccessTokenValid(token, handicap = DEFAULT_RENEW_HANDICAP_MS) {
|
|
18
|
+
const expireTime = token.expire_time;
|
|
19
|
+
return expireTime - handicap > Date.now();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class RingCentral {
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this._options = options;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
loginUrl({
|
|
28
|
+
state,
|
|
29
|
+
}) {
|
|
30
|
+
const query = {
|
|
31
|
+
response_type: 'code',
|
|
32
|
+
redirect_uri: this._options.redirectUri,
|
|
33
|
+
client_id: this._options.clientId,
|
|
34
|
+
response_hint: 'brand_id contracted_country_code',
|
|
35
|
+
};
|
|
36
|
+
if (state) {
|
|
37
|
+
query.state = state;
|
|
38
|
+
}
|
|
39
|
+
return `${this._options.server}/restapi/oauth/authorize?${stringifyQuery(query)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async generateToken({ code }) {
|
|
43
|
+
const body = {
|
|
44
|
+
code,
|
|
45
|
+
grant_type: 'authorization_code',
|
|
46
|
+
redirect_uri: this._options.redirectUri,
|
|
47
|
+
};
|
|
48
|
+
const response = await this._tokenRequest('/restapi/oauth/token', body);
|
|
49
|
+
if (Number.parseInt(response.status, 10) >= 400) {
|
|
50
|
+
throw new Error('Generate Token error', response.status);
|
|
51
|
+
}
|
|
52
|
+
const {
|
|
53
|
+
expires_in,
|
|
54
|
+
refresh_token_expires_in,
|
|
55
|
+
scope,
|
|
56
|
+
endpoint_id, // do no save this field into db to reduce db size
|
|
57
|
+
...token
|
|
58
|
+
} = await response.json();
|
|
59
|
+
return {
|
|
60
|
+
...token,
|
|
61
|
+
expire_time: Date.now() + parseInt(expires_in, 10) * 1000,
|
|
62
|
+
refresh_token_expire_time: Date.now() + parseInt(refresh_token_expires_in, 10) * 1000,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async refreshToken(token) {
|
|
67
|
+
const body = {
|
|
68
|
+
grant_type: 'refresh_token',
|
|
69
|
+
refresh_token: token.refresh_token,
|
|
70
|
+
access_token_ttl: token.expires_in,
|
|
71
|
+
refresh_token_ttl: token.refresh_token_expires_in,
|
|
72
|
+
};
|
|
73
|
+
const response = await this._tokenRequest('/restapi/oauth/token', body);
|
|
74
|
+
if (Number.parseInt(response.status, 10) >= 400) {
|
|
75
|
+
const error = new Error('Refresh Token error', response.status);
|
|
76
|
+
error.response = response;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
const {
|
|
80
|
+
expires_in,
|
|
81
|
+
refresh_token_expires_in,
|
|
82
|
+
scope,
|
|
83
|
+
endpoint_id, // do no save this field into db to reduce db size
|
|
84
|
+
...newToken
|
|
85
|
+
} = await response.json();
|
|
86
|
+
return {
|
|
87
|
+
...newToken,
|
|
88
|
+
expire_time: Date.now() + parseInt(expires_in, 10) * 1000,
|
|
89
|
+
refresh_token_expire_time: Date.now() + parseInt(refresh_token_expires_in, 10) * 1000,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async revokeToken(token) {
|
|
94
|
+
const body = {
|
|
95
|
+
token: token.access_token,
|
|
96
|
+
};
|
|
97
|
+
const response = await this._tokenRequest('/restapi/oauth/revoke', body);
|
|
98
|
+
if (Number.parseInt(response.status, 10) >= 400) {
|
|
99
|
+
throw new Error('Revoke Token error', response.status);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async _tokenRequest(path, body) {
|
|
104
|
+
const authorization = `${this._options.clientId}:${this._options.clientSecret}`;
|
|
105
|
+
const response = await fetch(
|
|
106
|
+
`${this._options.server}${path}`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
body: stringifyQuery(body),
|
|
109
|
+
headers: {
|
|
110
|
+
'Accept': 'application/json',
|
|
111
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
112
|
+
'Authorization': `Basic ${Buffer.from(authorization).toString('base64')}`
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
return response;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async request({
|
|
120
|
+
server = this._options.server,
|
|
121
|
+
path,
|
|
122
|
+
query,
|
|
123
|
+
body,
|
|
124
|
+
method,
|
|
125
|
+
accept = 'application/json',
|
|
126
|
+
}, token) {
|
|
127
|
+
let uri = `${server}${path}`;
|
|
128
|
+
if (query) {
|
|
129
|
+
uri = uri + (uri.includes('?') ? '&' : '?') + stringifyQuery(query);
|
|
130
|
+
}
|
|
131
|
+
const response = await fetch(uri, {
|
|
132
|
+
method,
|
|
133
|
+
body: body ? JSON.stringify(body) : body,
|
|
134
|
+
headers: {
|
|
135
|
+
'Accept': accept,
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
'Authorization': `${token.token_type} ${token.access_token}`,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
if (Number.parseInt(response.status, 10) >= 400) {
|
|
141
|
+
const error = new Error(`request data error ${response.status}`);
|
|
142
|
+
const errorText = await response.text();
|
|
143
|
+
error.message = errorText;
|
|
144
|
+
error.response = response;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
return response;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async createSubscription({
|
|
151
|
+
eventFilters,
|
|
152
|
+
webhookUri,
|
|
153
|
+
}, token) {
|
|
154
|
+
const response = await this.request({
|
|
155
|
+
method: 'POST',
|
|
156
|
+
path: '/restapi/v1.0/subscription',
|
|
157
|
+
body: {
|
|
158
|
+
eventFilters,
|
|
159
|
+
deliveryMode: {
|
|
160
|
+
transportType: 'WebHook',
|
|
161
|
+
address: webhookUri,
|
|
162
|
+
},
|
|
163
|
+
expiresIn: 7 * 24 * 3600, // 7 days
|
|
164
|
+
},
|
|
165
|
+
}, token);
|
|
166
|
+
const {
|
|
167
|
+
uri,
|
|
168
|
+
creationTime,
|
|
169
|
+
deliveryMode,
|
|
170
|
+
status, // do no save those field into db to reduce db size
|
|
171
|
+
...subscription
|
|
172
|
+
} = await response.json();
|
|
173
|
+
return subscription;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async getExtensionInfo(extensionId, token) {
|
|
177
|
+
const response = await this.request({
|
|
178
|
+
method: 'GET',
|
|
179
|
+
path: `/restapi/v1.0/account/~/extension/${extensionId}`,
|
|
180
|
+
}, token);
|
|
181
|
+
return response.json();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async getAccountInfo(token) {
|
|
185
|
+
const response = await this.request({
|
|
186
|
+
method: 'GET',
|
|
187
|
+
path: `/restapi/v1.0/account/~`,
|
|
188
|
+
}, token);
|
|
189
|
+
return response.json();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async getCallsAggregationData({ token, timezone, timeFrom, timeTo }) {
|
|
193
|
+
const body = {
|
|
194
|
+
grouping: {
|
|
195
|
+
groupBy: "Company"
|
|
196
|
+
},
|
|
197
|
+
timeSettings: {
|
|
198
|
+
timeZone: timezone,
|
|
199
|
+
timeRange: {
|
|
200
|
+
timeFrom: timeFrom,
|
|
201
|
+
timeTo: timeTo
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
responseOptions: {
|
|
205
|
+
counters: {
|
|
206
|
+
callsByDirection: {
|
|
207
|
+
aggregationType: "Sum"
|
|
208
|
+
},
|
|
209
|
+
callsByResponse: {
|
|
210
|
+
aggregationType: "Sum"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
timers: {
|
|
214
|
+
allCallsDuration: {
|
|
215
|
+
aggregationType: "Sum"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const response = await this.request({
|
|
221
|
+
method: 'POST',
|
|
222
|
+
path: `/analytics/calls/v1/accounts/~/aggregation/fetch`,
|
|
223
|
+
body,
|
|
224
|
+
accept: 'application/json'
|
|
225
|
+
}, token);
|
|
226
|
+
return response.json();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getCallLogData({ extensionId = '~', token, timezone, timeFrom, timeTo }) {
|
|
230
|
+
let pageStart = 1;
|
|
231
|
+
let isFinalPage = false;
|
|
232
|
+
let callLogResponse = null;
|
|
233
|
+
let result = { records: [] };
|
|
234
|
+
while (!isFinalPage) {
|
|
235
|
+
callLogResponse = await this.request({
|
|
236
|
+
method: 'GET',
|
|
237
|
+
path: `/restapi/v1.0/account/~/extension/${extensionId}/call-log?dateFrom=${timeFrom}&dateTo=${timeTo}&page=${pageStart}&view=Simple&perPage=1000`,
|
|
238
|
+
}, token);
|
|
239
|
+
const resultJson = await callLogResponse.json();
|
|
240
|
+
result.records.push(...resultJson.records);
|
|
241
|
+
if (resultJson.navigation?.nextPage) {
|
|
242
|
+
pageStart++;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
isFinalPage = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
async getSMSData({ extensionId = '~', token, timezone, timeFrom, timeTo }) {
|
|
251
|
+
let pageStart = 1;
|
|
252
|
+
let isFinalPage = false;
|
|
253
|
+
let smsLogResponse = null;
|
|
254
|
+
let result = { records: [] };
|
|
255
|
+
while (!isFinalPage) {
|
|
256
|
+
smsLogResponse = await this.request({
|
|
257
|
+
method: 'GET',
|
|
258
|
+
path: `/restapi/v1.0/account/~/extension/${extensionId}/message-store?dateFrom=${timeFrom}&dateTo=${timeTo}&page=${pageStart}&perPage=100`,
|
|
259
|
+
}, token);
|
|
260
|
+
const resultJson = await smsLogResponse.json();
|
|
261
|
+
result.records.push(...resultJson.records);
|
|
262
|
+
if (resultJson.navigation?.nextPage) {
|
|
263
|
+
pageStart++;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
isFinalPage = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
exports.RingCentral = RingCentral;
|
|
274
|
+
exports.isRefreshTokenValid = isRefreshTokenValid;
|
|
275
|
+
exports.isAccessTokenValid = isAccessTokenValid;
|
package/lib/util.js
CHANGED
|
@@ -37,6 +37,20 @@ function secondsToHoursMinutesSeconds(seconds) {
|
|
|
37
37
|
return resultString;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function getMostRecentDate({ allDateValues }) {
|
|
41
|
+
var result = 0;
|
|
42
|
+
for (const date of allDateValues) {
|
|
43
|
+
if(!date)
|
|
44
|
+
{
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (date > result) {
|
|
48
|
+
result = date;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
// media reader link: https://ringcentral.github.io/ringcentral-media-reader/?media=https://media.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/message-store/{messageId}/content/{contentId}
|
|
41
55
|
// platform media link: https://media.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/message-store/{messageId}/content/{contentId}
|
|
42
56
|
function getMediaReaderLinkByPlatformMediaLink(platformMediaLink){
|
|
@@ -50,4 +64,5 @@ function getMediaReaderLinkByPlatformMediaLink(platformMediaLink){
|
|
|
50
64
|
exports.getTimeZone = getTimeZone;
|
|
51
65
|
exports.getHashValue = getHashValue;
|
|
52
66
|
exports.secondsToHoursMinutesSeconds = secondsToHoursMinutesSeconds;
|
|
67
|
+
exports.getMostRecentDate = getMostRecentDate;
|
|
53
68
|
exports.getMediaReaderLinkByPlatformMediaLink = getMediaReaderLinkByPlatformMediaLink;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const Sequelize = require('sequelize');
|
|
2
2
|
const { sequelize } = require('./sequelize');
|
|
3
3
|
|
|
4
|
-
// Model for
|
|
4
|
+
// Model for Admin data
|
|
5
5
|
exports.AdminConfigModel = sequelize.define('adminConfigs', {
|
|
6
6
|
// hashed rc account ID
|
|
7
7
|
id: {
|
|
@@ -14,7 +14,16 @@ exports.AdminConfigModel = sequelize.define('adminConfigs', {
|
|
|
14
14
|
customAdapter: {
|
|
15
15
|
type: Sequelize.JSON
|
|
16
16
|
},
|
|
17
|
-
|
|
17
|
+
adminAccessToken: {
|
|
18
|
+
type: Sequelize.STRING(512),
|
|
19
|
+
},
|
|
20
|
+
adminRefreshToken: {
|
|
21
|
+
type: Sequelize.STRING(512),
|
|
22
|
+
},
|
|
23
|
+
adminTokenExpiry: {
|
|
24
|
+
type: Sequelize.DATE
|
|
25
|
+
},
|
|
26
|
+
// Array of:
|
|
18
27
|
// {
|
|
19
28
|
// crmUserId: string,
|
|
20
29
|
// rcExtensionId: array of strings
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const Sequelize = require('sequelize');
|
|
2
|
+
const { sequelize } = require('./sequelize');
|
|
3
|
+
|
|
4
|
+
exports.CallDownListModel = sequelize.define('callDownLists', {
|
|
5
|
+
id: {
|
|
6
|
+
type: Sequelize.STRING,
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
},
|
|
9
|
+
userId: {
|
|
10
|
+
type: Sequelize.STRING,
|
|
11
|
+
},
|
|
12
|
+
contactId: {
|
|
13
|
+
type: Sequelize.STRING,
|
|
14
|
+
},
|
|
15
|
+
contactType: {
|
|
16
|
+
type: Sequelize.STRING,
|
|
17
|
+
},
|
|
18
|
+
status: {
|
|
19
|
+
type: Sequelize.STRING,
|
|
20
|
+
},
|
|
21
|
+
scheduledAt: {
|
|
22
|
+
type: Sequelize.DATE,
|
|
23
|
+
},
|
|
24
|
+
lastCallAt: {
|
|
25
|
+
type: Sequelize.DATE,
|
|
26
|
+
}
|
|
27
|
+
}, {
|
|
28
|
+
timestamps: true,
|
|
29
|
+
indexes: [
|
|
30
|
+
{ fields: ['userId'] },
|
|
31
|
+
{ fields: ['status'] },
|
|
32
|
+
{ fields: ['scheduledAt'] },
|
|
33
|
+
{ fields: ['userId', 'status'] }
|
|
34
|
+
]
|
|
35
|
+
});
|
package/models/userModel.js
CHANGED
package/package.json
CHANGED
package/releaseNotes.json
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"1.6.9": {
|
|
3
|
-
"global": [
|
|
4
|
-
{
|
|
5
|
-
"type": "New",
|
|
6
|
-
"description": "- Server-side logging now supports multiple RingCentral users to be mapped under one CRM user"
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
"type": "Fix",
|
|
10
|
-
"description": "- Server-side logging user mapping not working properly when running under multi-site scenarios"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
13
|
-
},
|
|
14
|
-
"1.6.8": {
|
|
15
|
-
"global": [
|
|
16
|
-
{
|
|
17
|
-
"type": "Fix",
|
|
18
|
-
"description": "- Server-side logging, if it's created by one admin, the other admin will be able to see its status"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"type": "Fix",
|
|
22
|
-
"description": "- Conference calls show wrong warning message"
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
2
|
"1.6.7": {
|
|
27
3
|
"global": [
|
|
28
4
|
{
|