@app-connect/core 1.7.10 → 1.7.11
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/developerPortal.js +43 -0
- package/connector/proxy/index.js +10 -3
- package/connector/registry.js +8 -6
- package/handlers/admin.js +44 -21
- package/handlers/auth.js +89 -67
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +4 -104
- package/handlers/disposition.js +4 -142
- package/handlers/log.js +172 -257
- package/handlers/user.js +19 -6
- package/index.js +213 -47
- package/lib/analytics.js +3 -1
- package/lib/authSession.js +68 -0
- package/lib/callLogComposer.js +498 -420
- package/lib/errorHandler.js +206 -0
- package/lib/jwt.js +2 -0
- package/lib/logger.js +190 -0
- package/lib/oauth.js +21 -10
- package/lib/ringcentral.js +2 -10
- package/lib/sharedSMSComposer.js +471 -0
- package/mcp/SupportedPlatforms.md +12 -0
- package/mcp/lib/validator.js +91 -0
- package/mcp/mcpHandler.js +166 -0
- package/mcp/tools/checkAuthStatus.js +90 -0
- package/mcp/tools/collectAuthInfo.js +86 -0
- package/mcp/tools/createCallLog.js +299 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +185 -0
- package/mcp/tools/findContactByName.js +87 -0
- package/mcp/tools/findContactByPhone.js +96 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getHelp.js +39 -0
- package/mcp/tools/getPublicConnectors.js +46 -0
- package/mcp/tools/index.js +58 -0
- package/mcp/tools/logout.js +63 -0
- package/mcp/tools/rcGetCallLogs.js +73 -0
- package/mcp/tools/setConnector.js +64 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/cacheModel.js +3 -0
- package/package.json +71 -70
- package/releaseNotes.json +12 -0
- package/test/handlers/log.test.js +6 -2
- package/test/lib/logger.test.js +206 -0
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/mcp/tools/collectAuthInfo.test.js +192 -0
- package/test/mcp/tools/createCallLog.test.js +412 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +363 -0
- package/test/mcp/tools/findContactByName.test.js +263 -0
- package/test/mcp/tools/findContactByPhone.test.js +284 -0
- package/test/mcp/tools/getCallLog.test.js +286 -0
- package/test/mcp/tools/getPublicConnectors.test.js +128 -0
- package/test/mcp/tools/logout.test.js +169 -0
- package/test/mcp/tools/setConnector.test.js +177 -0
- package/test/mcp/tools/updateCallLog.test.js +346 -0
package/index.js
CHANGED
|
@@ -6,7 +6,6 @@ const dynamoose = require('dynamoose');
|
|
|
6
6
|
const axios = require('axios');
|
|
7
7
|
const { UserModel } = require('./models/userModel');
|
|
8
8
|
const { CallDownListModel } = require('./models/callDownListModel');
|
|
9
|
-
const { Op } = require('sequelize');
|
|
10
9
|
const { CallLogModel } = require('./models/callLogModel');
|
|
11
10
|
const { MessageLogModel } = require('./models/messageLogModel');
|
|
12
11
|
const { AdminConfigModel } = require('./models/adminConfigModel');
|
|
@@ -26,14 +25,19 @@ const analytics = require('./lib/analytics');
|
|
|
26
25
|
const util = require('./lib/util');
|
|
27
26
|
const connectorRegistry = require('./connector/registry');
|
|
28
27
|
const calldown = require('./handlers/calldown');
|
|
28
|
+
const mcpHandler = require('./mcp/mcpHandler');
|
|
29
|
+
const logger = require('./lib/logger');
|
|
29
30
|
const { DebugTracer } = require('./lib/debugTracer');
|
|
30
31
|
const s3ErrorLogReport = require('./lib/s3ErrorLogReport');
|
|
32
|
+
const { handleDatabaseError } = require('./lib/errorHandler');
|
|
33
|
+
const { updateAuthSession } = require('./lib/authSession');
|
|
31
34
|
|
|
32
35
|
let packageJson = null;
|
|
33
36
|
try {
|
|
34
37
|
packageJson = require('./package.json');
|
|
35
38
|
}
|
|
36
39
|
catch (e) {
|
|
40
|
+
logger.error('Error loading package.json', { stack: e.stack });
|
|
37
41
|
packageJson = require('../package.json');
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -52,7 +56,7 @@ axios.defaults.headers.common['Unified-CRM-Extension-Version'] = packageJson.ver
|
|
|
52
56
|
|
|
53
57
|
async function initDB() {
|
|
54
58
|
if (!process.env.DISABLE_SYNC_DB_TABLE) {
|
|
55
|
-
|
|
59
|
+
logger.info('creating db tables if not exist...');
|
|
56
60
|
await UserModel.sync();
|
|
57
61
|
await CallLogModel.sync();
|
|
58
62
|
await MessageLogModel.sync();
|
|
@@ -129,6 +133,7 @@ function createCoreRouter() {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
catch (e) {
|
|
136
|
+
logger.error('Error getting crm manifest', { stack: e.stack });
|
|
132
137
|
res.status(400).send('Platform not found');
|
|
133
138
|
}
|
|
134
139
|
});
|
|
@@ -281,7 +286,7 @@ function createCoreRouter() {
|
|
|
281
286
|
}
|
|
282
287
|
}
|
|
283
288
|
catch (e) {
|
|
284
|
-
|
|
289
|
+
logger.error('Auth validation failed', { platform: platformName, stack: e.stack });
|
|
285
290
|
tracer?.traceError('authValidation:error', e);
|
|
286
291
|
statusCode = e.response?.status ?? 'unknown';
|
|
287
292
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -333,7 +338,7 @@ function createCoreRouter() {
|
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
340
|
catch (e) {
|
|
336
|
-
|
|
341
|
+
logger.error('Set admin settings failed', { stack: e.stack });
|
|
337
342
|
tracer?.traceError('setAdminSettings:error', e);
|
|
338
343
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
339
344
|
success = false;
|
|
@@ -458,7 +463,7 @@ function createCoreRouter() {
|
|
|
458
463
|
}
|
|
459
464
|
}
|
|
460
465
|
catch (e) {
|
|
461
|
-
|
|
466
|
+
logger.error('Get user mapping failed', { stack: e.stack });
|
|
462
467
|
tracer?.traceError('getUserMapping:error', e);
|
|
463
468
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
464
469
|
}
|
|
@@ -509,7 +514,7 @@ function createCoreRouter() {
|
|
|
509
514
|
success = true;
|
|
510
515
|
}
|
|
511
516
|
catch (e) {
|
|
512
|
-
|
|
517
|
+
logger.error('Get server logging settings failed', { stack: e.stack });
|
|
513
518
|
tracer?.traceError('getServerLoggingSettings:error', e);
|
|
514
519
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
515
520
|
}
|
|
@@ -565,7 +570,7 @@ function createCoreRouter() {
|
|
|
565
570
|
success = true;
|
|
566
571
|
}
|
|
567
572
|
catch (e) {
|
|
568
|
-
|
|
573
|
+
logger.error('Set server logging settings failed', { stack: e.stack });
|
|
569
574
|
tracer?.traceError('setServerLoggingSettings:error', e);
|
|
570
575
|
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: { messageType: 'warning', message: 'Server logging settings update failed', ttl: 5000 } }) : { successful: false, returnMessage: { messageType: 'warning', message: 'Server logging settings update failed', ttl: 5000 } });
|
|
571
576
|
success = false;
|
|
@@ -601,7 +606,7 @@ function createCoreRouter() {
|
|
|
601
606
|
}
|
|
602
607
|
}
|
|
603
608
|
catch (e) {
|
|
604
|
-
|
|
609
|
+
logger.error('Get user preload settings failed', { stack: e.stack });
|
|
605
610
|
tracer?.traceError('getUserSettingsByAdmin:error', e);
|
|
606
611
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
607
612
|
}
|
|
@@ -640,7 +645,7 @@ function createCoreRouter() {
|
|
|
640
645
|
}
|
|
641
646
|
}
|
|
642
647
|
catch (e) {
|
|
643
|
-
|
|
648
|
+
logger.error('Get user settings failed', { platform: platformName, stack: e.stack });
|
|
644
649
|
tracer?.traceError('getUserSettings:error', e, { platform: platformName });
|
|
645
650
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
646
651
|
}
|
|
@@ -693,7 +698,7 @@ function createCoreRouter() {
|
|
|
693
698
|
}
|
|
694
699
|
}
|
|
695
700
|
catch (e) {
|
|
696
|
-
|
|
701
|
+
logger.error('Set user settings failed', { platform: platformName, stack: e.stack });
|
|
697
702
|
tracer?.traceError('setUserSettings:error', e, { platform: platformName });
|
|
698
703
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
699
704
|
}
|
|
@@ -733,7 +738,7 @@ function createCoreRouter() {
|
|
|
733
738
|
}
|
|
734
739
|
}
|
|
735
740
|
catch (e) {
|
|
736
|
-
|
|
741
|
+
logger.error('Get hostname failed', { stack: e.stack });
|
|
737
742
|
tracer?.traceError('hostname:error', e);
|
|
738
743
|
res.status(500).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
739
744
|
}
|
|
@@ -744,17 +749,28 @@ function createCoreRouter() {
|
|
|
744
749
|
tracer?.trace('oauth-callback:start', { query: req.query });
|
|
745
750
|
let platformName = null;
|
|
746
751
|
let success = false;
|
|
752
|
+
let sessionId = null;
|
|
747
753
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
748
754
|
try {
|
|
749
755
|
if (!req.query?.callbackUri || req.query.callbackUri === 'undefined') {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
756
|
+
// case: from mcp
|
|
757
|
+
if (req.query.code) {
|
|
758
|
+
// eslint-disable-next-line no-param-reassign
|
|
759
|
+
req.query.callbackUri = `${process.env.APP_SERVER}/oauth-callback?code=${req.query.code}`;
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
tracer?.trace('oauth-callback:missingCallbackUri', {});
|
|
763
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Missing callbackUri') : 'Missing callbackUri');
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
753
766
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
767
|
+
const state = new URL(req.query.callbackUri).searchParams.get('state') ?? req.query.state;
|
|
768
|
+
const stateParams = new URLSearchParams(state ? decodeURIComponent(state) : '');
|
|
769
|
+
platformName = stateParams.get('platform');
|
|
770
|
+
// Extract mcp auth sessionId if present
|
|
771
|
+
sessionId = stateParams?.get('sessionId');
|
|
772
|
+
const isFromMCP = !!sessionId;
|
|
773
|
+
const hostname = req.query.hostname ?? stateParams.get('hostname');
|
|
758
774
|
const tokenUrl = req.query.tokenUrl;
|
|
759
775
|
if (!platformName) {
|
|
760
776
|
tracer?.trace('oauth-callback:missingPlatformName', {});
|
|
@@ -770,25 +786,53 @@ function createCoreRouter() {
|
|
|
770
786
|
platform: platformName,
|
|
771
787
|
hostname,
|
|
772
788
|
tokenUrl,
|
|
773
|
-
query: req.query
|
|
789
|
+
query: req.query,
|
|
790
|
+
proxyId: req.query.proxyId,
|
|
791
|
+
isFromMCP
|
|
774
792
|
});
|
|
775
793
|
if (userInfo) {
|
|
776
794
|
const jwtToken = jwt.generateJwt({
|
|
777
795
|
id: userInfo.id.toString(),
|
|
778
796
|
platform: platformName
|
|
779
797
|
});
|
|
780
|
-
|
|
781
|
-
|
|
798
|
+
// Store in session if sessionId exists (MCP flow)
|
|
799
|
+
if (isFromMCP) {
|
|
800
|
+
await updateAuthSession(sessionId, {
|
|
801
|
+
status: 'completed',
|
|
802
|
+
jwtToken,
|
|
803
|
+
userInfo: {
|
|
804
|
+
id: userInfo.id,
|
|
805
|
+
name: userInfo.name
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
res.status(200).send("Authentication successful. Please go back to AI Agent and confirm it.");
|
|
809
|
+
success = true;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
|
|
813
|
+
success = true;
|
|
814
|
+
}
|
|
782
815
|
}
|
|
783
816
|
else {
|
|
784
817
|
res.status(200).send(tracer ? tracer.wrapResponse({ returnMessage }) : { returnMessage });
|
|
785
|
-
|
|
818
|
+
await updateAuthSession(sessionId, {
|
|
819
|
+
status: 'failed',
|
|
820
|
+
errorMessage: returnMessage?.message || 'Authentication failed'
|
|
821
|
+
});
|
|
786
822
|
}
|
|
823
|
+
success = false;
|
|
787
824
|
}
|
|
788
825
|
catch (e) {
|
|
789
|
-
|
|
826
|
+
logger.error('OAuth callback failed', { platform: platformName, stack: e.stack });
|
|
790
827
|
tracer?.traceError('oauth-callback:error', e, { platform: platformName });
|
|
791
828
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
829
|
+
if (sessionId) {
|
|
830
|
+
await updateAuthSession(sessionId, {
|
|
831
|
+
status: 'failed',
|
|
832
|
+
errorMessage: e.message || e.toString()
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
792
836
|
success = false;
|
|
793
837
|
}
|
|
794
838
|
const requestEndTime = new Date().getTime();
|
|
@@ -805,7 +849,7 @@ function createCoreRouter() {
|
|
|
805
849
|
author,
|
|
806
850
|
eventAddedVia
|
|
807
851
|
});
|
|
808
|
-
})
|
|
852
|
+
});
|
|
809
853
|
router.post('/apiKeyLogin', async function (req, res) {
|
|
810
854
|
const requestStartTime = new Date().getTime();
|
|
811
855
|
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
@@ -845,7 +889,7 @@ function createCoreRouter() {
|
|
|
845
889
|
}
|
|
846
890
|
}
|
|
847
891
|
catch (e) {
|
|
848
|
-
|
|
892
|
+
logger.error('API key login failed', { platform: platformName, stack: e.stack });
|
|
849
893
|
tracer?.traceError('apiKeyLogin:error', e, { platform: platformName });
|
|
850
894
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
851
895
|
success = false;
|
|
@@ -895,7 +939,7 @@ function createCoreRouter() {
|
|
|
895
939
|
}
|
|
896
940
|
}
|
|
897
941
|
catch (e) {
|
|
898
|
-
|
|
942
|
+
logger.error('Unauthorize failed', { platform: platformName, stack: e.stack });
|
|
899
943
|
tracer?.traceError('unAuthorize:error', e, { platform: platformName });
|
|
900
944
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
901
945
|
success = false;
|
|
@@ -924,7 +968,7 @@ function createCoreRouter() {
|
|
|
924
968
|
res.status(200).send(tracer ? tracer.wrapResponse({ extensionId, accountId }) : { extensionId, accountId });
|
|
925
969
|
}
|
|
926
970
|
catch (e) {
|
|
927
|
-
|
|
971
|
+
logger.error('Get user info hash failed', { stack: e.stack });
|
|
928
972
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
929
973
|
tracer?.traceError('userInfoHash:error', e);
|
|
930
974
|
}
|
|
@@ -978,7 +1022,7 @@ function createCoreRouter() {
|
|
|
978
1022
|
}
|
|
979
1023
|
}
|
|
980
1024
|
catch (e) {
|
|
981
|
-
|
|
1025
|
+
logger.error('Find contact failed', { platform: platformName, stack: e.stack });
|
|
982
1026
|
tracer?.traceError('findContact:error', e, { platform: platformName });
|
|
983
1027
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
984
1028
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1036,7 +1080,7 @@ function createCoreRouter() {
|
|
|
1036
1080
|
}
|
|
1037
1081
|
}
|
|
1038
1082
|
catch (e) {
|
|
1039
|
-
|
|
1083
|
+
logger.error('Create contact failed', { platform: platformName, stack: e.stack });
|
|
1040
1084
|
tracer?.traceError('createContact:error', e, { platform: platformName });
|
|
1041
1085
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1042
1086
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1079,7 +1123,7 @@ function createCoreRouter() {
|
|
|
1079
1123
|
}
|
|
1080
1124
|
const { id: userId, platform } = decodedToken;
|
|
1081
1125
|
platformName = platform;
|
|
1082
|
-
const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ sessionId: req.body.sessionId, note: req.body.note });
|
|
1126
|
+
const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ platform, userId, sessionId: req.body.sessionId, note: req.body.note });
|
|
1083
1127
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1084
1128
|
success = true;
|
|
1085
1129
|
if (extraDataTracking) {
|
|
@@ -1087,7 +1131,7 @@ function createCoreRouter() {
|
|
|
1087
1131
|
}
|
|
1088
1132
|
}
|
|
1089
1133
|
} catch (e) {
|
|
1090
|
-
|
|
1134
|
+
logger.error('Save note cache failed', { platform: platformName, stack: e.stack });
|
|
1091
1135
|
tracer?.traceError('saveNoteCache:error', e, { platform: platformName });
|
|
1092
1136
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1093
1137
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1101,6 +1145,14 @@ function createCoreRouter() {
|
|
|
1101
1145
|
accountId: hashedAccountId,
|
|
1102
1146
|
extensionId: hashedExtensionId,
|
|
1103
1147
|
success,
|
|
1148
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1149
|
+
userAgent,
|
|
1150
|
+
ip,
|
|
1151
|
+
author,
|
|
1152
|
+
eventAddedVia,
|
|
1153
|
+
extras: {
|
|
1154
|
+
...extraData
|
|
1155
|
+
}
|
|
1104
1156
|
});
|
|
1105
1157
|
})
|
|
1106
1158
|
router.get('/callLog', async function (req, res) {
|
|
@@ -1137,7 +1189,7 @@ function createCoreRouter() {
|
|
|
1137
1189
|
}
|
|
1138
1190
|
}
|
|
1139
1191
|
catch (e) {
|
|
1140
|
-
|
|
1192
|
+
logger.error('Get call log failed', { platform: platformName, stack: e.stack });
|
|
1141
1193
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1142
1194
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1143
1195
|
tracer?.traceError('getCallLog:error', e, { platform: platformName });
|
|
@@ -1194,7 +1246,7 @@ function createCoreRouter() {
|
|
|
1194
1246
|
}
|
|
1195
1247
|
}
|
|
1196
1248
|
catch (e) {
|
|
1197
|
-
|
|
1249
|
+
logger.error('Create call log failed', { platform: platformName, stack: e.stack });
|
|
1198
1250
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1199
1251
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1200
1252
|
tracer?.traceError('createCallLog:error', e, { platform: platformName });
|
|
@@ -1251,7 +1303,7 @@ function createCoreRouter() {
|
|
|
1251
1303
|
}
|
|
1252
1304
|
}
|
|
1253
1305
|
catch (e) {
|
|
1254
|
-
|
|
1306
|
+
logger.error('Update call log failed', { platform: platformName, stack: e.stack });
|
|
1255
1307
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1256
1308
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1257
1309
|
tracer?.traceError('updateCallLog:error', e, { platform: platformName });
|
|
@@ -1313,7 +1365,7 @@ function createCoreRouter() {
|
|
|
1313
1365
|
}
|
|
1314
1366
|
}
|
|
1315
1367
|
catch (e) {
|
|
1316
|
-
|
|
1368
|
+
logger.error('Upsert call disposition failed', { platform: platformName, stack: e.stack });
|
|
1317
1369
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1318
1370
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1319
1371
|
tracer?.traceError('upsertCallDisposition:error', e, { platform: platformName });
|
|
@@ -1371,7 +1423,7 @@ function createCoreRouter() {
|
|
|
1371
1423
|
}
|
|
1372
1424
|
}
|
|
1373
1425
|
catch (e) {
|
|
1374
|
-
|
|
1426
|
+
logger.error('Create message log failed', { platform: platformName, stack: e.stack });
|
|
1375
1427
|
statusCode = e.response?.status ?? 'unknown';
|
|
1376
1428
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1377
1429
|
tracer?.traceError('createMessageLog:error', e, { platform: platformName });
|
|
@@ -1411,11 +1463,16 @@ function createCoreRouter() {
|
|
|
1411
1463
|
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
1412
1464
|
return;
|
|
1413
1465
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1466
|
+
try {
|
|
1467
|
+
const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
|
|
1468
|
+
success = true;
|
|
1469
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, id }) : { successful: true, id });
|
|
1470
|
+
}
|
|
1471
|
+
catch (e) {
|
|
1472
|
+
return handleDatabaseError(e, 'Error scheduling call down');
|
|
1473
|
+
}
|
|
1417
1474
|
} catch (e) {
|
|
1418
|
-
|
|
1475
|
+
logger.error('Schedule call down failed', { platform: platformName, stack: e.stack });
|
|
1419
1476
|
statusCode = e.response?.status ?? 'unknown';
|
|
1420
1477
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1421
1478
|
tracer?.traceError('scheduleCallDown:error', e, { platform: platformName });
|
|
@@ -1458,7 +1515,7 @@ function createCoreRouter() {
|
|
|
1458
1515
|
success = true;
|
|
1459
1516
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, items }) : { successful: true, items });
|
|
1460
1517
|
} catch (e) {
|
|
1461
|
-
|
|
1518
|
+
logger.error('Get call down list failed', { platform: platformName, stack: e.stack });
|
|
1462
1519
|
statusCode = e.response?.status ?? 'unknown';
|
|
1463
1520
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1464
1521
|
tracer?.traceError('getCallDownList:error', e, { platform: platformName });
|
|
@@ -1506,7 +1563,7 @@ function createCoreRouter() {
|
|
|
1506
1563
|
success = true;
|
|
1507
1564
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
1508
1565
|
} catch (e) {
|
|
1509
|
-
|
|
1566
|
+
logger.error('Delete call down item failed', { platform: platformName, stack: e.stack });
|
|
1510
1567
|
statusCode = e.response?.status ?? 'unknown';
|
|
1511
1568
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1512
1569
|
tracer?.traceError('deleteCallDownItem:error', e, { platform: platformName });
|
|
@@ -1553,7 +1610,7 @@ function createCoreRouter() {
|
|
|
1553
1610
|
success = true;
|
|
1554
1611
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
1555
1612
|
} catch (e) {
|
|
1556
|
-
|
|
1613
|
+
logger.error('Mark call down called failed', { platform: platformName, stack: e.stack });
|
|
1557
1614
|
statusCode = e.response?.status ?? 'unknown';
|
|
1558
1615
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1559
1616
|
tracer?.traceError('markCallDownCalled:error', e, { platform: platformName });
|
|
@@ -1581,7 +1638,6 @@ function createCoreRouter() {
|
|
|
1581
1638
|
tracer?.trace('contactSearchByName:start', { query: req.query });
|
|
1582
1639
|
let platformName = null;
|
|
1583
1640
|
let success = false;
|
|
1584
|
-
let resultCount = 0;
|
|
1585
1641
|
let statusCode = 200;
|
|
1586
1642
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1587
1643
|
try {
|
|
@@ -1601,7 +1657,7 @@ function createCoreRouter() {
|
|
|
1601
1657
|
|
|
1602
1658
|
}
|
|
1603
1659
|
catch (e) {
|
|
1604
|
-
|
|
1660
|
+
logger.error('Contact search by name failed', { platform: platformName, stack: e.stack });
|
|
1605
1661
|
statusCode = e.response?.status ?? 'unknown';
|
|
1606
1662
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1607
1663
|
tracer?.traceError('contactSearchByName:error', e, { platform: platformName });
|
|
@@ -1651,7 +1707,7 @@ function createCoreRouter() {
|
|
|
1651
1707
|
success = false;
|
|
1652
1708
|
}
|
|
1653
1709
|
catch (e) {
|
|
1654
|
-
|
|
1710
|
+
logger.error('Get admin report failed', { stack: e.stack });
|
|
1655
1711
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1656
1712
|
tracer?.traceError('getAdminReport:error', e, { platform: platformName });
|
|
1657
1713
|
}
|
|
@@ -1696,7 +1752,7 @@ function createCoreRouter() {
|
|
|
1696
1752
|
success = false;
|
|
1697
1753
|
}
|
|
1698
1754
|
catch (e) {
|
|
1699
|
-
|
|
1755
|
+
logger.error('Get user report failed', { stack: e.stack });
|
|
1700
1756
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1701
1757
|
tracer?.traceError('getUserReport:error', e, { platform: platformName });
|
|
1702
1758
|
}
|
|
@@ -1822,6 +1878,116 @@ function createCoreRouter() {
|
|
|
1822
1878
|
}
|
|
1823
1879
|
});
|
|
1824
1880
|
}
|
|
1881
|
+
// --- METADATA ENDPOINT 1: Resource Metadata ---
|
|
1882
|
+
// Tells the client "I am protected" and "Here is who protects me"
|
|
1883
|
+
router.get('/.well-known/oauth-protected-resource', (req, res) => {
|
|
1884
|
+
res.json({
|
|
1885
|
+
resource: process.env.APP_SERVER,
|
|
1886
|
+
// CHANGE THIS: Point to your own server so the client fetches YOUR metadata next
|
|
1887
|
+
authorization_servers: [process.env.APP_SERVER],
|
|
1888
|
+
scopes_supported: ["ReadAccounts"]
|
|
1889
|
+
});
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
// --- METADATA ENDPOINT 2: Auth Server Metadata ---
|
|
1893
|
+
// Usually, you can redirect to your provider's configuration.
|
|
1894
|
+
// If your provider supports OIDC discovery, this is often sufficient.
|
|
1895
|
+
router.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
1896
|
+
res.json({
|
|
1897
|
+
issuer: process.env.APP_SERVER,
|
|
1898
|
+
registration_endpoint: `${process.env.APP_SERVER}/oauth/register`,
|
|
1899
|
+
|
|
1900
|
+
// CHANGE THIS: Don't point to RingCentral. Point to your own Shim.
|
|
1901
|
+
authorization_endpoint: `${process.env.APP_SERVER}/oauth/authorize_shim`,
|
|
1902
|
+
|
|
1903
|
+
// Keep the token endpoint pointing to RingCentral (that usually works fine)
|
|
1904
|
+
token_endpoint: `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/token`,
|
|
1905
|
+
token_endpoint_auth_methods_supported: ["client_secret_basic"],
|
|
1906
|
+
response_types_supported: ["code"]
|
|
1907
|
+
});
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
router.get('/oauth/authorize_shim', (req, res) => {
|
|
1911
|
+
// 1. Get the parameters ChatGPT sent us
|
|
1912
|
+
const { response_type, client_id, redirect_uri, state, scope } = req.query;
|
|
1913
|
+
|
|
1914
|
+
// 2. Rebuild the query string for RingCentral
|
|
1915
|
+
// We explicitly LEAVE OUT 'resource' and any other junk
|
|
1916
|
+
const params = new URLSearchParams({
|
|
1917
|
+
response_type,
|
|
1918
|
+
client_id, // This will be your RC Client ID (since ChatGPT got it from /register)
|
|
1919
|
+
redirect_uri,
|
|
1920
|
+
state,
|
|
1921
|
+
scope
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
// 3. Redirect the user's browser to the REAL RingCentral URL
|
|
1925
|
+
const rcUrl = `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/authorize?${params.toString()}`;
|
|
1926
|
+
|
|
1927
|
+
console.log("Proxying OAuth request to:", rcUrl); // Helpful for debugging
|
|
1928
|
+
res.redirect(rcUrl);
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
router.post('/oauth/register', (req, res) => {
|
|
1932
|
+
// The MCP client calls this to get credentials.
|
|
1933
|
+
// We simply return our hardcoded RingCentral app credentials.
|
|
1934
|
+
res.json({
|
|
1935
|
+
client_id: process.env.RINGCENTRAL_CLIENT_ID,
|
|
1936
|
+
client_secret: process.env.RINGCENTRAL_CLIENT_SECRET
|
|
1937
|
+
});
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
router.use('/mcp', (req, res, next) => {// LOG EVERYTHING
|
|
1941
|
+
console.log(`[${req.method}] /mcp`);
|
|
1942
|
+
console.log("Headers:", JSON.stringify(req.headers['authorization'] ? "Auth Token Present" : "No Auth"));
|
|
1943
|
+
console.log("Body:", JSON.stringify(req.body));
|
|
1944
|
+
// return next();
|
|
1945
|
+
// Capture the response finish to see the status code
|
|
1946
|
+
res.on('finish', () => {
|
|
1947
|
+
console.log(`[Response] Status: ${res.statusCode}`);
|
|
1948
|
+
console.log(`[Response] data: ${JSON.stringify(res.data)}`);
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
const authHeader = req.headers.authorization;
|
|
1952
|
+
const token = authHeader?.split(' ')[1]; // Remove "Bearer "
|
|
1953
|
+
// Allow the initial connection (GET) and CORS checks (OPTIONS) to pass freely.
|
|
1954
|
+
// We only want to block the actual commands (POST).
|
|
1955
|
+
if (req.method === 'GET' || req.method === 'OPTIONS') {
|
|
1956
|
+
return next();
|
|
1957
|
+
}
|
|
1958
|
+
// SCENARIO 1: No Token provided. Kick off the OAuth flow.
|
|
1959
|
+
if (!token) {
|
|
1960
|
+
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
1961
|
+
return res.status(401).send();
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// SCENARIO 2: Token provided. Verify it.
|
|
1965
|
+
try {
|
|
1966
|
+
next();
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
console.error("Token validation failed:", error.message);
|
|
1969
|
+
// Token is invalid or expired
|
|
1970
|
+
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
1971
|
+
return res.status(401).send();
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
// Handle OPTIONS for CORS preflight
|
|
1975
|
+
router.options('/mcp', (req, res) => {
|
|
1976
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1977
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
1978
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
1979
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
1980
|
+
res.status(200).end();
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// Dedicated endpoint for all MCP traffic
|
|
1984
|
+
router.post('/mcp', async (req, res) => {
|
|
1985
|
+
// Set CORS headers
|
|
1986
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1987
|
+
res.setHeader('Content-Type', 'application/json');
|
|
1988
|
+
|
|
1989
|
+
await mcpHandler.handleMcpRequest(req, res);
|
|
1990
|
+
});
|
|
1825
1991
|
|
|
1826
1992
|
return router;
|
|
1827
1993
|
}
|
package/lib/analytics.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const Mixpanel = require('mixpanel');
|
|
2
2
|
const parser = require('ua-parser-js');
|
|
3
|
+
const logger = require('./logger');
|
|
3
4
|
let packageJson = null;
|
|
4
5
|
try {
|
|
5
6
|
packageJson = require('../package.json');
|
|
6
7
|
}
|
|
7
8
|
catch (e) {
|
|
9
|
+
logger.warn('Error loading package.json', { stack: e.stack });
|
|
8
10
|
packageJson = require('../../package.json');
|
|
9
11
|
}
|
|
10
12
|
const appName = 'App Connect';
|
|
@@ -49,5 +51,5 @@ exports.track = function track({ eventName, interfaceName, connectorName, accoun
|
|
|
49
51
|
author,
|
|
50
52
|
...extras
|
|
51
53
|
});
|
|
52
|
-
|
|
54
|
+
logger.info(`Event: ${eventName}`);
|
|
53
55
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Session Helper
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for managing OAuth auth sessions using CacheModel
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { CacheModel } = require('../models/cacheModel');
|
|
8
|
+
|
|
9
|
+
const AUTH_SESSION_PREFIX = 'auth-session';
|
|
10
|
+
const SESSION_EXPIRY_MINUTES = 5;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a new auth session
|
|
14
|
+
*/
|
|
15
|
+
async function createAuthSession(sessionId, data) {
|
|
16
|
+
await CacheModel.create({
|
|
17
|
+
id: `${AUTH_SESSION_PREFIX}-${sessionId}`,
|
|
18
|
+
cacheKey: AUTH_SESSION_PREFIX,
|
|
19
|
+
userId: sessionId,
|
|
20
|
+
status: 'pending',
|
|
21
|
+
data: {
|
|
22
|
+
...data,
|
|
23
|
+
createdAt: new Date().toISOString()
|
|
24
|
+
},
|
|
25
|
+
expiry: new Date(Date.now() + SESSION_EXPIRY_MINUTES * 60 * 1000)
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get an auth session by ID
|
|
31
|
+
*/
|
|
32
|
+
async function getAuthSession(sessionId) {
|
|
33
|
+
const record = await CacheModel.findByPk(`${AUTH_SESSION_PREFIX}-${sessionId}`);
|
|
34
|
+
|
|
35
|
+
if (!record) return null;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
sessionId: record.userId,
|
|
39
|
+
status: record.status,
|
|
40
|
+
...record.data
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Update an auth session
|
|
46
|
+
*/
|
|
47
|
+
async function updateAuthSession(sessionId, data) {
|
|
48
|
+
const record = await CacheModel.findByPk(`${AUTH_SESSION_PREFIX}-${sessionId}`);
|
|
49
|
+
|
|
50
|
+
if (!record) return;
|
|
51
|
+
|
|
52
|
+
const existingData = record.data || {};
|
|
53
|
+
await record.update({
|
|
54
|
+
status: data.status || record.status,
|
|
55
|
+
data: {
|
|
56
|
+
...existingData,
|
|
57
|
+
...data,
|
|
58
|
+
updatedAt: new Date().toISOString()
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
createAuthSession,
|
|
65
|
+
getAuthSession,
|
|
66
|
+
updateAuthSession
|
|
67
|
+
};
|
|
68
|
+
|