@app-connect/core 1.7.35 → 1.7.36
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/connector/mock.js +9 -4
- package/connector/proxy/engine.js +3 -2
- package/connector/proxy/index.js +2 -2
- package/docs/architecture.md +1 -0
- package/docs/handlers.md +1 -1
- package/docs/models.md +2 -0
- package/docs/routes.md +1 -1
- package/handlers/disposition.js +3 -2
- package/handlers/log.js +16 -6
- package/handlers/plugin.js +13 -6
- package/index.js +10 -4
- package/lib/callLogLookup.js +82 -9
- package/lib/migrateCallLogsSchema.js +128 -7
- package/models/callLogModel.js +6 -0
- package/package.json +1 -1
- package/releaseNotes.json +16 -0
- package/test/connector/developerPortal.test.js +166 -0
- package/test/connector/mock.test.js +131 -0
- package/test/connector/proxy/engine.test.js +85 -0
- package/test/connector/proxy/index.test.js +246 -0
- package/test/connector/proxy/sample.json +1 -0
- package/test/handlers/admin.test.js +344 -0
- package/test/handlers/appointment.test.js +260 -0
- package/test/handlers/calldown.test.js +310 -0
- package/test/handlers/disposition.test.js +396 -0
- package/test/handlers/log.test.js +324 -1
- package/test/handlers/managedOAuth.test.js +262 -0
- package/test/handlers/plugin.test.js +305 -0
- package/test/handlers/user.test.js +381 -0
- package/test/index.test.js +102 -0
- package/test/lib/analytics.test.js +146 -0
- package/test/lib/authSession.test.js +173 -0
- package/test/lib/encode.test.js +59 -0
- package/test/lib/errorHandler.test.js +246 -0
- package/test/lib/generalErrorMessage.test.js +82 -0
- package/test/lib/s3ErrorLogReport.test.js +187 -0
- package/test/mcp/mcpHandlerMore.test.js +384 -0
- package/test/mcp/tools/appointmentTools.test.js +362 -0
- package/test/models/callDownListModel.test.js +125 -0
- package/test/models/dynamo/lockSchema.test.js +37 -0
- package/test/models/dynamo/noteCacheSchema.test.js +45 -0
- package/test/models/llmSessionModel.test.js +91 -0
- package/test/models/models.test.js +92 -0
- package/test/routes/calldownRoutes.test.js +224 -0
- package/test/routes/coreRouterBroadRoutes.test.js +855 -0
- package/test/routes/dispositionRoutes.test.js +192 -0
- package/test/routes/managedAuthRoutes.test.js +151 -0
- package/test/routes/pluginRoutes.test.js +262 -0
package/connector/mock.js
CHANGED
|
@@ -27,19 +27,21 @@ async function deleteUser() {
|
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async function getCallLog({ sessionIds, extensionNumber }) {
|
|
30
|
+
async function getCallLog({ sessionIds, extensionNumber, hashedExtensionId }) {
|
|
31
31
|
const sessionIdsArray = sessionIds.split(',');
|
|
32
32
|
const extensionNumberValue = extensionNumber?.toString() ?? '';
|
|
33
|
+
const hashedExtensionIdValue = hashedExtensionId?.toString() ?? '';
|
|
33
34
|
const callLogs = await CallLogModel.findAll({
|
|
34
35
|
where: buildCallLogSessionWhere({
|
|
35
36
|
sessionIds: sessionIdsArray,
|
|
36
37
|
extensionNumber: extensionNumberValue,
|
|
38
|
+
hashedExtensionId: hashedExtensionIdValue,
|
|
37
39
|
}),
|
|
38
|
-
order: [['extensionNumber', 'ASC']]
|
|
40
|
+
order: [['hashedExtensionId', 'ASC'], ['extensionNumber', 'ASC']]
|
|
39
41
|
});
|
|
40
42
|
const logs = [];
|
|
41
43
|
for (const sId of sessionIdsArray) {
|
|
42
|
-
const callLog = findMatchingCallLog(callLogs, sId, extensionNumberValue);
|
|
44
|
+
const callLog = findMatchingCallLog(callLogs, sId, extensionNumberValue, hashedExtensionIdValue);
|
|
43
45
|
if (!callLog) {
|
|
44
46
|
logs.push({ sessionId: sId, matched: false });
|
|
45
47
|
}
|
|
@@ -51,12 +53,14 @@ async function getCallLog({ sessionIds, extensionNumber }) {
|
|
|
51
53
|
return logs;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
async function createCallLog({ sessionId, extensionNumber }) {
|
|
56
|
+
async function createCallLog({ sessionId, extensionNumber, hashedExtensionId }) {
|
|
55
57
|
const extensionNumberValue = extensionNumber?.toString() ?? '';
|
|
58
|
+
const hashedExtensionIdValue = hashedExtensionId?.toString() ?? '';
|
|
56
59
|
let callLog = await CallLogModel.findOne({
|
|
57
60
|
where: buildCallLogSessionWhere({
|
|
58
61
|
sessionId,
|
|
59
62
|
extensionNumber: extensionNumberValue,
|
|
63
|
+
hashedExtensionId: hashedExtensionIdValue,
|
|
60
64
|
})
|
|
61
65
|
});
|
|
62
66
|
if (!callLog) {
|
|
@@ -64,6 +68,7 @@ async function createCallLog({ sessionId, extensionNumber }) {
|
|
|
64
68
|
id: shortid.generate(),
|
|
65
69
|
sessionId,
|
|
66
70
|
extensionNumber: extensionNumberValue,
|
|
71
|
+
hashedExtensionId: hashedExtensionIdValue,
|
|
67
72
|
userId: 'mockUser'
|
|
68
73
|
});
|
|
69
74
|
}
|
|
@@ -109,8 +109,8 @@ async function performRequest({ config, opName, inputs, user, authHeader }) {
|
|
|
109
109
|
return response;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function mapFindContactResponse({ config, response }) {
|
|
113
|
-
const map = config.operations?.
|
|
112
|
+
function mapFindContactResponse({ config, response, opName = 'findContact' }) {
|
|
113
|
+
const map = config.operations?.[opName]?.responseMapping;
|
|
114
114
|
if (!map) return [];
|
|
115
115
|
const __ctx = { body: response.data };
|
|
116
116
|
const list = getByPath(__ctx, map.listPath || 'body') || [];
|
|
@@ -123,6 +123,7 @@ function mapFindContactResponse({ config, response }) {
|
|
|
123
123
|
type: getByPath(it, itemMap.typePath || 'type') || 'Contact',
|
|
124
124
|
title: getByPath(it, itemMap.titlePath || 'title') || "",
|
|
125
125
|
company: getByPath(it, itemMap.companyPath || 'company') || "",
|
|
126
|
+
createdDate: getByPath(it, itemMap.createdDatePath || 'createdDate') || undefined,
|
|
126
127
|
mostRecentActivityDate: getByPath(it, itemMap.mostRecentActivityDatePath || 'mostRecentActivityDate') || undefined,
|
|
127
128
|
additionalInfo: getByPath(it, itemMap.additionalInfoPath || 'additionalInfo') || null
|
|
128
129
|
};
|
package/connector/proxy/index.js
CHANGED
|
@@ -179,7 +179,7 @@ async function findContact({ user, authHeader, phoneNumber, overridingFormat, is
|
|
|
179
179
|
user,
|
|
180
180
|
authHeader
|
|
181
181
|
});
|
|
182
|
-
const matchedContactInfo = mapFindContactResponse({ config: cfg, response });
|
|
182
|
+
const matchedContactInfo = mapFindContactResponse({ config: cfg, response, opName: 'findContact' });
|
|
183
183
|
return {
|
|
184
184
|
successful: true,
|
|
185
185
|
matchedContactInfo,
|
|
@@ -225,7 +225,7 @@ async function findContactWithName({ user, authHeader, name, proxyConfig }) {
|
|
|
225
225
|
user,
|
|
226
226
|
authHeader
|
|
227
227
|
});
|
|
228
|
-
const matchedContactInfo = mapFindContactResponse({ config: cfg, response });
|
|
228
|
+
const matchedContactInfo = mapFindContactResponse({ config: cfg, response, opName: 'findContactWithName' });
|
|
229
229
|
return {
|
|
230
230
|
successful: true,
|
|
231
231
|
matchedContactInfo,
|
package/docs/architecture.md
CHANGED
|
@@ -26,6 +26,7 @@ Its job is to expose a stable HTTP surface, persist shared state, and delegate C
|
|
|
26
26
|
- installs an axios interceptor in local-style environments
|
|
27
27
|
- syncs Sequelize models on startup unless `DISABLE_SYNC_DB_TABLE` or `skipDatabaseInit` disables it
|
|
28
28
|
- adds the `hashedRcExtensionId` column to `users` if an older schema is missing it
|
|
29
|
+
- migrates `callLogs` to include `hashedExtensionId` in the local call-log identity key when an older schema is missing it
|
|
29
30
|
- mounts all shared HTTP routes
|
|
30
31
|
- exposes dev-only mock routes when `IS_PROD === 'false'`
|
|
31
32
|
|
package/docs/handlers.md
CHANGED
|
@@ -59,7 +59,7 @@ This is the heaviest handler in the package.
|
|
|
59
59
|
|
|
60
60
|
Key responsibilities:
|
|
61
61
|
|
|
62
|
-
- prevents duplicate call-log creation by checking `CallLogModel` on `sessionId`
|
|
62
|
+
- prevents duplicate call-log creation by checking `CallLogModel` on `sessionId` plus extension identity (`hashedExtensionId` first, with legacy `extensionNumber` fallback)
|
|
63
63
|
- loads note cache from DynamoDB when `USE_CACHE` and server-side call logging are enabled
|
|
64
64
|
- runs configured synchronous plugins before creating or updating logs
|
|
65
65
|
- dispatches configured asynchronous call plugins after call-log create or update succeeds
|
package/docs/models.md
CHANGED
|
@@ -35,6 +35,8 @@ Stores the mapping between telephony sessions and CRM call logs.
|
|
|
35
35
|
| --- | --- |
|
|
36
36
|
| `id` | Telephony call id |
|
|
37
37
|
| `sessionId` | Session id; also part of the composite primary key |
|
|
38
|
+
| `extensionNumber` | Legacy RingCentral extension number identity; part of the composite primary key |
|
|
39
|
+
| `hashedExtensionId` | Hashed RingCentral extension id used for current call-log identity; part of the composite primary key |
|
|
38
40
|
| `platform` | Connector platform |
|
|
39
41
|
| `thirdPartyLogId` | CRM log id |
|
|
40
42
|
| `userId` | App Connect user id |
|
package/docs/routes.md
CHANGED
|
@@ -75,7 +75,7 @@ This page documents the non-MCP HTTP routes defined in `index.js`.
|
|
|
75
75
|
| Method | Path | Purpose |
|
|
76
76
|
| --- | --- | --- |
|
|
77
77
|
| `POST` | `/callLog/cacheNote` | Stores a temporary note for server-side call logging |
|
|
78
|
-
| `GET` | `/callLog` | Looks up existing call logs by session id |
|
|
78
|
+
| `GET` | `/callLog` | Looks up existing call logs by session id plus extension identity |
|
|
79
79
|
| `POST` | `/callLog` | Creates a CRM call log and stores the local linkage record |
|
|
80
80
|
| `PATCH` | `/callLog` | Updates an existing CRM call log |
|
|
81
81
|
| `PUT` | `/callDisposition` | Upserts call disposition data through the connector |
|
package/handlers/disposition.js
CHANGED
|
@@ -6,12 +6,13 @@ const { Connector } = require('../models/dynamo/connectorSchema');
|
|
|
6
6
|
const { handleApiError } = require('../lib/errorHandler');
|
|
7
7
|
const { buildCallLogSessionWhere } = require('../lib/callLogLookup');
|
|
8
8
|
|
|
9
|
-
async function upsertCallDisposition({ platform, userId, sessionId, extensionNumber, dispositions }) {
|
|
9
|
+
async function upsertCallDisposition({ platform, userId, sessionId, extensionNumber, hashedExtensionId, dispositions }) {
|
|
10
10
|
try {
|
|
11
11
|
const existingCallLog = await CallLogModel.findOne({
|
|
12
12
|
where: buildCallLogSessionWhere({
|
|
13
13
|
sessionId,
|
|
14
14
|
extensionNumber,
|
|
15
|
+
hashedExtensionId,
|
|
15
16
|
})
|
|
16
17
|
});
|
|
17
18
|
if (!existingCallLog) {
|
|
@@ -75,7 +76,7 @@ async function upsertCallDisposition({ platform, userId, sessionId, extensionNum
|
|
|
75
76
|
return { successful: !!logId, logId, returnMessage, extraDataTracking };
|
|
76
77
|
}
|
|
77
78
|
catch (e) {
|
|
78
|
-
return handleApiError(e, platform, 'upsertCallDisposition', { userId, sessionId, extensionNumber, dispositions });
|
|
79
|
+
return handleApiError(e, platform, 'upsertCallDisposition', { userId, sessionId, extensionNumber, hashedExtensionId, dispositions });
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
package/handlers/log.js
CHANGED
|
@@ -20,6 +20,7 @@ const { AccountDataModel } = require('../models/accountDataModel');
|
|
|
20
20
|
const pluginCore = require('./plugin');
|
|
21
21
|
const {
|
|
22
22
|
getCallLogExtensionNumber,
|
|
23
|
+
getCallLogHashedExtensionId,
|
|
23
24
|
buildCallLogSessionWhere,
|
|
24
25
|
findMatchingCallLog,
|
|
25
26
|
} = require('../lib/callLogLookup');
|
|
@@ -73,6 +74,7 @@ function buildAsyncPluginTaskData({ taskId, callbackUrl, pluginId, platform, ope
|
|
|
73
74
|
rcAccountId: user.rcAccountId,
|
|
74
75
|
sessionId: logInfo.sessionId ?? incomingData?.sessionId,
|
|
75
76
|
extensionNumber: getCallLogExtensionNumber(incomingData),
|
|
77
|
+
hashedExtensionId: getCallLogHashedExtensionId(incomingData),
|
|
76
78
|
callLogId: existingCallLog?.id ?? logInfo.telephonySessionId ?? logInfo.id,
|
|
77
79
|
thirdPartyLogId: existingCallLog?.thirdPartyLogId,
|
|
78
80
|
contactId: existingCallLog?.contactId ?? incomingData?.contactId,
|
|
@@ -307,6 +309,7 @@ async function appendAsyncPluginNoteToCallLog({ taskCache, note }) {
|
|
|
307
309
|
...buildCallLogSessionWhere({
|
|
308
310
|
sessionId: taskData.sessionId,
|
|
309
311
|
extensionNumber: taskData.extensionNumber,
|
|
312
|
+
hashedExtensionId: taskData.hashedExtensionId,
|
|
310
313
|
}),
|
|
311
314
|
platform: taskData.platform,
|
|
312
315
|
userId: taskData.userId,
|
|
@@ -448,12 +451,14 @@ async function handleAsyncPluginCallback({ taskId, body }) {
|
|
|
448
451
|
async function createCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
449
452
|
try {
|
|
450
453
|
const extensionNumber = getCallLogExtensionNumber(incomingData);
|
|
454
|
+
const hashedExtensionId = getCallLogHashedExtensionId(incomingData);
|
|
451
455
|
let existingCallLog = null;
|
|
452
456
|
try {
|
|
453
457
|
existingCallLog = await CallLogModel.findOne({
|
|
454
458
|
where: buildCallLogSessionWhere({
|
|
455
459
|
sessionId: incomingData.logInfo.sessionId,
|
|
456
460
|
extensionNumber,
|
|
461
|
+
hashedExtensionId,
|
|
457
462
|
})
|
|
458
463
|
});
|
|
459
464
|
}
|
|
@@ -612,6 +617,7 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
612
617
|
id: incomingData.logInfo.telephonySessionId || incomingData.logInfo.id,
|
|
613
618
|
sessionId: incomingData.logInfo.sessionId,
|
|
614
619
|
extensionNumber,
|
|
620
|
+
hashedExtensionId,
|
|
615
621
|
platform,
|
|
616
622
|
thirdPartyLogId: logId,
|
|
617
623
|
userId,
|
|
@@ -651,7 +657,7 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
651
657
|
}
|
|
652
658
|
}
|
|
653
659
|
|
|
654
|
-
async function getCallLog({ userId, sessionIds, extensionNumber, platform, requireDetails }) {
|
|
660
|
+
async function getCallLog({ userId, sessionIds, extensionNumber, hashedExtensionId, platform, requireDetails }) {
|
|
655
661
|
try {
|
|
656
662
|
let user = await UserModel.findByPk(userId);
|
|
657
663
|
if (!user || !user.accessToken) {
|
|
@@ -706,15 +712,16 @@ async function getCallLog({ userId, sessionIds, extensionNumber, platform, requi
|
|
|
706
712
|
where: buildCallLogSessionWhere({
|
|
707
713
|
sessionIds: sessionIdsArray,
|
|
708
714
|
extensionNumber,
|
|
715
|
+
hashedExtensionId,
|
|
709
716
|
}),
|
|
710
|
-
order: [['extensionNumber', 'ASC']]
|
|
717
|
+
order: [['hashedExtensionId', 'ASC'], ['extensionNumber', 'ASC']]
|
|
711
718
|
});
|
|
712
719
|
for (const sId of sessionIdsArray) {
|
|
713
720
|
if (sId == 0) {
|
|
714
721
|
logs.push({ sessionId: sId, matched: false });
|
|
715
722
|
continue;
|
|
716
723
|
}
|
|
717
|
-
const callLog = findMatchingCallLog(callLogs, sId, extensionNumber);
|
|
724
|
+
const callLog = findMatchingCallLog(callLogs, sId, extensionNumber, hashedExtensionId);
|
|
718
725
|
if (!callLog) {
|
|
719
726
|
logs.push({ sessionId: sId, matched: false });
|
|
720
727
|
}
|
|
@@ -731,11 +738,12 @@ async function getCallLog({ userId, sessionIds, extensionNumber, platform, requi
|
|
|
731
738
|
where: buildCallLogSessionWhere({
|
|
732
739
|
sessionIds: sessionIdsArray,
|
|
733
740
|
extensionNumber,
|
|
741
|
+
hashedExtensionId,
|
|
734
742
|
}),
|
|
735
|
-
order: [['extensionNumber', 'ASC']]
|
|
743
|
+
order: [['hashedExtensionId', 'ASC'], ['extensionNumber', 'ASC']]
|
|
736
744
|
});
|
|
737
745
|
for (const sId of sessionIdsArray) {
|
|
738
|
-
const callLog = findMatchingCallLog(callLogs, sId, extensionNumber);
|
|
746
|
+
const callLog = findMatchingCallLog(callLogs, sId, extensionNumber, hashedExtensionId);
|
|
739
747
|
if (!callLog) {
|
|
740
748
|
logs.push({ sessionId: sId, matched: false });
|
|
741
749
|
}
|
|
@@ -747,19 +755,21 @@ async function getCallLog({ userId, sessionIds, extensionNumber, platform, requi
|
|
|
747
755
|
return { successful: true, logs, returnMessage, extraDataTracking };
|
|
748
756
|
}
|
|
749
757
|
catch (e) {
|
|
750
|
-
return handleApiError(e, platform, 'getCallLog', { userId, sessionIds, requireDetails, extensionNumber });
|
|
758
|
+
return handleApiError(e, platform, 'getCallLog', { userId, sessionIds, requireDetails, extensionNumber, hashedExtensionId });
|
|
751
759
|
}
|
|
752
760
|
}
|
|
753
761
|
|
|
754
762
|
async function updateCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
755
763
|
try {
|
|
756
764
|
const extensionNumber = getCallLogExtensionNumber(incomingData);
|
|
765
|
+
const hashedExtensionId = getCallLogHashedExtensionId(incomingData);
|
|
757
766
|
let existingCallLog = null;
|
|
758
767
|
try {
|
|
759
768
|
existingCallLog = await CallLogModel.findOne({
|
|
760
769
|
where: buildCallLogSessionWhere({
|
|
761
770
|
sessionId: incomingData.sessionId,
|
|
762
771
|
extensionNumber,
|
|
772
|
+
hashedExtensionId,
|
|
763
773
|
})
|
|
764
774
|
});
|
|
765
775
|
}
|
package/handlers/plugin.js
CHANGED
|
@@ -48,7 +48,14 @@ async function getPluginLicenseStatus({ rcAccountId, pluginId }) {
|
|
|
48
48
|
'Authorization': `Bearer ${accountData.data.jwtToken}`
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
-
|
|
51
|
+
const licenseStatus = licenseStatusResponse.data;
|
|
52
|
+
if (!licenseStatus || !Object.prototype.hasOwnProperty.call(licenseStatus, 'licenseStatus')) {
|
|
53
|
+
return {
|
|
54
|
+
licenseStatus: false,
|
|
55
|
+
licenseStatusDescription: 'Plugin license status unavailable'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return licenseStatus;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
function getRefreshedJwtTokenFromHeaders({ headers }) {
|
|
@@ -58,15 +65,15 @@ function getRefreshedJwtTokenFromHeaders({ headers }) {
|
|
|
58
65
|
return headers['x-refreshed-jwt-token'] || headers['X-Refreshed-Jwt-Token'] || null;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
async function resolvePluginManifest({ pluginId, pluginAccess,
|
|
68
|
+
async function resolvePluginManifest({ pluginId, pluginAccess, ownerRcAccountId, pluginName }) {
|
|
62
69
|
const manifestFetchers = [];
|
|
63
70
|
if (pluginAccess === 'public') {
|
|
64
71
|
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
|
|
65
72
|
} else if (pluginAccess === 'private' || pluginAccess === 'shared') {
|
|
66
|
-
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${
|
|
73
|
+
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${ownerRcAccountId}`);
|
|
67
74
|
} else {
|
|
68
75
|
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
|
|
69
|
-
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${
|
|
76
|
+
manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${ownerRcAccountId}`);
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
let pluginData = null;
|
|
@@ -132,8 +139,8 @@ async function persistPluginData({ rcAccountId, pluginId, jwtToken, pluginData =
|
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
async function registerPluginAccount({ pluginId, rcAccessToken, rcAccountId, pluginAccess, pluginName }) {
|
|
136
|
-
const { pluginManifest } = await resolvePluginManifest({ pluginId, pluginAccess,
|
|
142
|
+
async function registerPluginAccount({ pluginId, rcAccessToken, rcAccountId, pluginAccess, pluginName, ownerRcAccountId }) {
|
|
143
|
+
const { pluginManifest } = await resolvePluginManifest({ pluginId, pluginAccess, ownerRcAccountId, pluginName });
|
|
137
144
|
if (!pluginManifest?.endpointUrl) {
|
|
138
145
|
throw new Error(`Plugin endpoint URL not found for ${pluginId}`);
|
|
139
146
|
}
|
package/index.js
CHANGED
|
@@ -33,6 +33,8 @@ const logger = require('./lib/logger');
|
|
|
33
33
|
const { DebugTracer } = require('./lib/debugTracer');
|
|
34
34
|
const s3ErrorLogReport = require('./lib/s3ErrorLogReport');
|
|
35
35
|
const pluginCore = require('./handlers/plugin');
|
|
36
|
+
const { sequelize } = require('./models/sequelize');
|
|
37
|
+
const { ensureCallLogsHashedExtensionIdSchema } = require('./lib/migrateCallLogsSchema');
|
|
36
38
|
const { handleDatabaseError } = require('./lib/errorHandler');
|
|
37
39
|
const { updateAuthSession } = require('./lib/authSession');
|
|
38
40
|
const managedAuthCore = require('./handlers/managedAuth');
|
|
@@ -71,6 +73,7 @@ async function initDB() {
|
|
|
71
73
|
await UserModel.sync();
|
|
72
74
|
await LlmSessionModel.sync();
|
|
73
75
|
await CallLogModel.sync();
|
|
76
|
+
await ensureCallLogsHashedExtensionIdSchema(sequelize);
|
|
74
77
|
await MessageLogModel.sync();
|
|
75
78
|
await AdminConfigModel.sync();
|
|
76
79
|
await CacheModel.sync();
|
|
@@ -1040,7 +1043,7 @@ function createCoreRouter() {
|
|
|
1040
1043
|
if (jwtToken) {
|
|
1041
1044
|
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
1042
1045
|
platformName = unAuthData?.platform ?? 'Unknown';
|
|
1043
|
-
if(!unAuthData || !unAuthData?.id) {
|
|
1046
|
+
if (!unAuthData || !unAuthData?.id) {
|
|
1044
1047
|
tracer?.trace('getUserSettings:noToken', {});
|
|
1045
1048
|
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
1046
1049
|
return;
|
|
@@ -2036,6 +2039,7 @@ function createCoreRouter() {
|
|
|
2036
2039
|
userId,
|
|
2037
2040
|
sessionIds: req.query.sessionIds,
|
|
2038
2041
|
extensionNumber: req.query.extensionNumber,
|
|
2042
|
+
hashedExtensionId: req.query.hashedExtensionId,
|
|
2039
2043
|
platform,
|
|
2040
2044
|
requireDetails: req.query.requireDetails === 'true'
|
|
2041
2045
|
});
|
|
@@ -2232,6 +2236,7 @@ function createCoreRouter() {
|
|
|
2232
2236
|
userId,
|
|
2233
2237
|
sessionId: req.body.sessionId,
|
|
2234
2238
|
extensionNumber: req.body.extensionNumber,
|
|
2239
|
+
hashedExtensionId: req.body.hashedExtensionId,
|
|
2235
2240
|
dispositions: req.body.dispositions,
|
|
2236
2241
|
additionalSubmission: req.body.additionalSubmission
|
|
2237
2242
|
});
|
|
@@ -2758,7 +2763,7 @@ function createCoreRouter() {
|
|
|
2758
2763
|
let success = false;
|
|
2759
2764
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
2760
2765
|
try {
|
|
2761
|
-
const { pluginId, rcAccountId, pluginAccess, pluginName } = req.body || {};
|
|
2766
|
+
const { pluginId, rcAccountId, pluginAccess, pluginName, ownerRcAccountId } = req.body || {};
|
|
2762
2767
|
const rcAccessToken = req.query?.rcAccessToken;
|
|
2763
2768
|
if (!pluginId || !rcAccountId) {
|
|
2764
2769
|
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'pluginId and rcAccountId are required' }) : { successful: false, returnMessage: 'pluginId and rcAccountId are required' });
|
|
@@ -2782,6 +2787,7 @@ function createCoreRouter() {
|
|
|
2782
2787
|
pluginId,
|
|
2783
2788
|
rcAccessToken,
|
|
2784
2789
|
rcAccountId: rcAccountId?.toString(),
|
|
2790
|
+
ownerRcAccountId: ownerRcAccountId?.toString(),
|
|
2785
2791
|
pluginAccess,
|
|
2786
2792
|
pluginName,
|
|
2787
2793
|
});
|
|
@@ -2920,7 +2926,7 @@ function createCoreRouter() {
|
|
|
2920
2926
|
router.get('/mockCallLog', async function (req, res) {
|
|
2921
2927
|
const secretKey = req.query.secretKey;
|
|
2922
2928
|
if (secretKey === process.env.APP_SERVER_SECRET_KEY) {
|
|
2923
|
-
const callLogs = await mock.getCallLog({ sessionIds: req.query.sessionIds, extensionNumber: req.query.extensionNumber });
|
|
2929
|
+
const callLogs = await mock.getCallLog({ sessionIds: req.query.sessionIds, extensionNumber: req.query.extensionNumber, hashedExtensionId: req.query.hashedExtensionId });
|
|
2924
2930
|
res.status(200).send(callLogs);
|
|
2925
2931
|
}
|
|
2926
2932
|
else {
|
|
@@ -2930,7 +2936,7 @@ function createCoreRouter() {
|
|
|
2930
2936
|
router.post('/mockCallLog', async function (req, res) {
|
|
2931
2937
|
const secretKey = req.query.secretKey;
|
|
2932
2938
|
if (secretKey === process.env.APP_SERVER_SECRET_KEY) {
|
|
2933
|
-
await mock.createCallLog({ sessionId: req.body.sessionId, extensionNumber: req.body.extensionNumber });
|
|
2939
|
+
await mock.createCallLog({ sessionId: req.body.sessionId, extensionNumber: req.body.extensionNumber, hashedExtensionId: req.body.hashedExtensionId });
|
|
2934
2940
|
res.status(200).send('Mock call log created');
|
|
2935
2941
|
}
|
|
2936
2942
|
else {
|
package/lib/callLogLookup.js
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
const Op = require('sequelize').Op;
|
|
2
|
+
const { getHashValue } = require('./util');
|
|
3
|
+
|
|
4
|
+
function stringOrEmpty(value) {
|
|
5
|
+
return value == null ? '' : value.toString();
|
|
6
|
+
}
|
|
2
7
|
|
|
3
8
|
function getCallLogExtensionNumber(incomingData) {
|
|
4
|
-
return (incomingData?.extensionNumber ?? incomingData?.logInfo?.extensionNumber)
|
|
9
|
+
return stringOrEmpty(incomingData?.extensionNumber ?? incomingData?.logInfo?.extensionNumber);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getCallLogHashedExtensionId(incomingData, hashKey = process.env.HASH_KEY) {
|
|
13
|
+
const hashedExtensionId = incomingData?.hashedExtensionId ?? incomingData?.logInfo?.hashedExtensionId;
|
|
14
|
+
if (hashedExtensionId) {
|
|
15
|
+
return hashedExtensionId.toString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rcExtensionId = incomingData?.rcExtensionId ?? incomingData?.logInfo?.rcExtensionId;
|
|
19
|
+
if (rcExtensionId && hashKey) {
|
|
20
|
+
return getHashValue(rcExtensionId.toString(), hashKey);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildLegacyIdentityWhere(extensionNumberValue) {
|
|
27
|
+
return {
|
|
28
|
+
extensionNumber: extensionNumberValue,
|
|
29
|
+
[Op.or]: [
|
|
30
|
+
{ hashedExtensionId: '' },
|
|
31
|
+
{ hashedExtensionId: null },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
5
34
|
}
|
|
6
35
|
|
|
7
|
-
function buildCallLogSessionWhere({ sessionId, sessionIds, extensionNumber }) {
|
|
36
|
+
function buildCallLogSessionWhere({ sessionId, sessionIds, extensionNumber, hashedExtensionId }) {
|
|
8
37
|
const where = {};
|
|
9
38
|
if (sessionIds) {
|
|
10
39
|
where.sessionId = {
|
|
@@ -14,21 +43,65 @@ function buildCallLogSessionWhere({ sessionId, sessionIds, extensionNumber }) {
|
|
|
14
43
|
else {
|
|
15
44
|
where.sessionId = sessionId;
|
|
16
45
|
}
|
|
17
|
-
|
|
18
|
-
|
|
46
|
+
|
|
47
|
+
const extensionNumberValue = stringOrEmpty(extensionNumber);
|
|
48
|
+
const hashedExtensionIdValue = stringOrEmpty(hashedExtensionId);
|
|
49
|
+
|
|
50
|
+
if (hashedExtensionIdValue) {
|
|
51
|
+
const identityWhere = [
|
|
52
|
+
{ hashedExtensionId: hashedExtensionIdValue },
|
|
53
|
+
];
|
|
54
|
+
if (extensionNumberValue) {
|
|
55
|
+
identityWhere.push(buildLegacyIdentityWhere(extensionNumberValue));
|
|
56
|
+
}
|
|
57
|
+
identityWhere.push(buildLegacyIdentityWhere(''));
|
|
58
|
+
where[Op.or] = identityWhere;
|
|
59
|
+
}
|
|
60
|
+
else if (extensionNumberValue) {
|
|
19
61
|
where.extensionNumber = extensionNumberValue;
|
|
20
62
|
}
|
|
21
63
|
return where;
|
|
22
64
|
}
|
|
23
65
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
66
|
+
function hasEmptyHashedExtensionId(callLog) {
|
|
67
|
+
return !stringOrEmpty(callLog.hashedExtensionId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findMatchingCallLog(callLogs, sessionId, extensionNumber, hashedExtensionId) {
|
|
71
|
+
const extensionNumberValue = stringOrEmpty(extensionNumber);
|
|
72
|
+
const hashedExtensionIdValue = stringOrEmpty(hashedExtensionId);
|
|
73
|
+
const sessionLogs = callLogs.filter(callLog => callLog.sessionId === sessionId);
|
|
74
|
+
|
|
75
|
+
if (hashedExtensionIdValue) {
|
|
76
|
+
const exactHashedLog = sessionLogs.find(callLog => (
|
|
77
|
+
stringOrEmpty(callLog.hashedExtensionId) === hashedExtensionIdValue
|
|
78
|
+
));
|
|
79
|
+
if (exactHashedLog) {
|
|
80
|
+
return exactHashedLog;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (extensionNumberValue) {
|
|
84
|
+
const legacyExtensionLog = sessionLogs.find(callLog => (
|
|
85
|
+
hasEmptyHashedExtensionId(callLog) &&
|
|
86
|
+
stringOrEmpty(callLog.extensionNumber) === extensionNumberValue
|
|
87
|
+
));
|
|
88
|
+
if (legacyExtensionLog) {
|
|
89
|
+
return legacyExtensionLog;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return sessionLogs.find(callLog => (
|
|
94
|
+
hasEmptyHashedExtensionId(callLog) &&
|
|
95
|
+
!stringOrEmpty(callLog.extensionNumber)
|
|
96
|
+
));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return sessionLogs.find(callLog => (
|
|
100
|
+
!extensionNumberValue || stringOrEmpty(callLog.extensionNumber) === extensionNumberValue
|
|
29
101
|
));
|
|
30
102
|
}
|
|
31
103
|
|
|
32
104
|
exports.getCallLogExtensionNumber = getCallLogExtensionNumber;
|
|
105
|
+
exports.getCallLogHashedExtensionId = getCallLogHashedExtensionId;
|
|
33
106
|
exports.buildCallLogSessionWhere = buildCallLogSessionWhere;
|
|
34
107
|
exports.findMatchingCallLog = findMatchingCallLog;
|