@app-connect/core 1.7.10 → 1.7.12
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 +135 -22
- package/handlers/auth.js +89 -67
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +4 -104
- package/handlers/disposition.js +7 -145
- package/handlers/log.js +174 -258
- package/handlers/user.js +19 -6
- package/index.js +280 -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 +110 -0
- package/mcp/tools/collectAuthInfo.js +91 -0
- package/mcp/tools/createCallLog.js +308 -0
- package/mcp/tools/createContact.js +117 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +190 -0
- package/mcp/tools/findContactByName.js +92 -0
- package/mcp/tools/findContactByPhone.js +101 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getGoogleFilePicker.js +103 -0
- package/mcp/tools/getHelp.js +44 -0
- package/mcp/tools/getPublicConnectors.js +53 -0
- package/mcp/tools/index.js +64 -0
- package/mcp/tools/logout.js +68 -0
- package/mcp/tools/rcGetCallLogs.js +78 -0
- package/mcp/tools/setConnector.js +69 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/cacheModel.js +3 -0
- package/package.json +71 -70
- package/releaseNotes.json +24 -0
- package/test/handlers/log.test.js +11 -4
- package/test/lib/logger.test.js +206 -0
- package/test/lib/ringcentral.test.js +0 -6
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/mcp/tools/collectAuthInfo.test.js +234 -0
- package/test/mcp/tools/createCallLog.test.js +425 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +376 -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/getGoogleFilePicker.test.js +281 -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
|
}
|
|
@@ -477,6 +482,63 @@ function createCoreRouter() {
|
|
|
477
482
|
eventAddedVia
|
|
478
483
|
});
|
|
479
484
|
});
|
|
485
|
+
router.post('/admin/reinitializeUserMapping', async function (req, res) {
|
|
486
|
+
const requestStartTime = new Date().getTime();
|
|
487
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
488
|
+
tracer?.trace('reinitializeUserMapping:start', { body: req.body });
|
|
489
|
+
let platformName = null;
|
|
490
|
+
let success = false;
|
|
491
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
492
|
+
try {
|
|
493
|
+
const jwtToken = req.query.jwtToken;
|
|
494
|
+
if (jwtToken) {
|
|
495
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
496
|
+
platformName = unAuthData?.platform ?? 'Unknown';
|
|
497
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
498
|
+
if (!user) {
|
|
499
|
+
tracer?.trace('reinitializeUserMapping:userNotFound', {});
|
|
500
|
+
res.status(400).send(tracer ? tracer.wrapResponse('User not found') : 'User not found');
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const { isValidated, rcAccountId } = await adminCore.validateAdminRole({ rcAccessToken: req.query.rcAccessToken });
|
|
504
|
+
const hashedRcAccountId = util.getHashValue(rcAccountId, process.env.HASH_KEY);
|
|
505
|
+
if (isValidated) {
|
|
506
|
+
const userMapping = await adminCore.reinitializeUserMapping({ user, hashedRcAccountId, rcExtensionList: req.body.rcExtensionList });
|
|
507
|
+
res.status(200).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
508
|
+
success = true;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
tracer?.trace('reinitializeUserMapping:adminValidationFailed', {});
|
|
512
|
+
res.status(401).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
513
|
+
success = true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
tracer?.trace('reinitializeUserMapping:noToken', {});
|
|
518
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
519
|
+
success = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
logger.error('Reinitialize user mapping failed', { stack: e.stack });
|
|
524
|
+
tracer?.traceError('reinitializeUserMapping:error', e);
|
|
525
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
526
|
+
}
|
|
527
|
+
const requestEndTime = new Date().getTime();
|
|
528
|
+
analytics.track({
|
|
529
|
+
eventName: 'Reinitialize user mapping',
|
|
530
|
+
interfaceName: 'reinitializeUserMapping',
|
|
531
|
+
connectorName: platformName,
|
|
532
|
+
accountId: hashedAccountId,
|
|
533
|
+
extensionId: hashedExtensionId,
|
|
534
|
+
success,
|
|
535
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
536
|
+
userAgent,
|
|
537
|
+
ip,
|
|
538
|
+
author,
|
|
539
|
+
eventAddedVia
|
|
540
|
+
});
|
|
541
|
+
});
|
|
480
542
|
router.get('/admin/serverLoggingSettings', async function (req, res) {
|
|
481
543
|
const requestStartTime = new Date().getTime();
|
|
482
544
|
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
@@ -509,7 +571,7 @@ function createCoreRouter() {
|
|
|
509
571
|
success = true;
|
|
510
572
|
}
|
|
511
573
|
catch (e) {
|
|
512
|
-
|
|
574
|
+
logger.error('Get server logging settings failed', { stack: e.stack });
|
|
513
575
|
tracer?.traceError('getServerLoggingSettings:error', e);
|
|
514
576
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
515
577
|
}
|
|
@@ -565,7 +627,7 @@ function createCoreRouter() {
|
|
|
565
627
|
success = true;
|
|
566
628
|
}
|
|
567
629
|
catch (e) {
|
|
568
|
-
|
|
630
|
+
logger.error('Set server logging settings failed', { stack: e.stack });
|
|
569
631
|
tracer?.traceError('setServerLoggingSettings:error', e);
|
|
570
632
|
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
633
|
success = false;
|
|
@@ -601,7 +663,7 @@ function createCoreRouter() {
|
|
|
601
663
|
}
|
|
602
664
|
}
|
|
603
665
|
catch (e) {
|
|
604
|
-
|
|
666
|
+
logger.error('Get user preload settings failed', { stack: e.stack });
|
|
605
667
|
tracer?.traceError('getUserSettingsByAdmin:error', e);
|
|
606
668
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
607
669
|
}
|
|
@@ -640,7 +702,7 @@ function createCoreRouter() {
|
|
|
640
702
|
}
|
|
641
703
|
}
|
|
642
704
|
catch (e) {
|
|
643
|
-
|
|
705
|
+
logger.error('Get user settings failed', { platform: platformName, stack: e.stack });
|
|
644
706
|
tracer?.traceError('getUserSettings:error', e, { platform: platformName });
|
|
645
707
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
646
708
|
}
|
|
@@ -693,7 +755,7 @@ function createCoreRouter() {
|
|
|
693
755
|
}
|
|
694
756
|
}
|
|
695
757
|
catch (e) {
|
|
696
|
-
|
|
758
|
+
logger.error('Set user settings failed', { platform: platformName, stack: e.stack });
|
|
697
759
|
tracer?.traceError('setUserSettings:error', e, { platform: platformName });
|
|
698
760
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
699
761
|
}
|
|
@@ -733,7 +795,7 @@ function createCoreRouter() {
|
|
|
733
795
|
}
|
|
734
796
|
}
|
|
735
797
|
catch (e) {
|
|
736
|
-
|
|
798
|
+
logger.error('Get hostname failed', { stack: e.stack });
|
|
737
799
|
tracer?.traceError('hostname:error', e);
|
|
738
800
|
res.status(500).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
739
801
|
}
|
|
@@ -744,17 +806,33 @@ function createCoreRouter() {
|
|
|
744
806
|
tracer?.trace('oauth-callback:start', { query: req.query });
|
|
745
807
|
let platformName = null;
|
|
746
808
|
let success = false;
|
|
809
|
+
let sessionId = null;
|
|
747
810
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
748
811
|
try {
|
|
749
812
|
if (!req.query?.callbackUri || req.query.callbackUri === 'undefined') {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
813
|
+
// case: from mcp
|
|
814
|
+
if (req.query.code) {
|
|
815
|
+
// eslint-disable-next-line no-param-reassign
|
|
816
|
+
req.query.callbackUri = `${process.env.APP_SERVER}/oauth-callback?code=${req.query.code}`;
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
tracer?.trace('oauth-callback:missingCallbackUri', {});
|
|
820
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Missing callbackUri') : 'Missing callbackUri');
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
753
823
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
824
|
+
const state = new URL(req.query.callbackUri).searchParams.get('state') ?? req.query.state;
|
|
825
|
+
const stateParams = new URLSearchParams(state ? decodeURIComponent(state) : '');
|
|
826
|
+
platformName = stateParams.get('platform');
|
|
827
|
+
// backward compatibility
|
|
828
|
+
if(!platformName)
|
|
829
|
+
{
|
|
830
|
+
platformName = req.query.callbackUri.split('platform=')[1];
|
|
831
|
+
}
|
|
832
|
+
// Extract mcp auth sessionId if present
|
|
833
|
+
sessionId = stateParams?.get('sessionId');
|
|
834
|
+
const isFromMCP = !!sessionId;
|
|
835
|
+
const hostname = req.query.hostname ?? stateParams.get('hostname');
|
|
758
836
|
const tokenUrl = req.query.tokenUrl;
|
|
759
837
|
if (!platformName) {
|
|
760
838
|
tracer?.trace('oauth-callback:missingPlatformName', {});
|
|
@@ -770,25 +848,53 @@ function createCoreRouter() {
|
|
|
770
848
|
platform: platformName,
|
|
771
849
|
hostname,
|
|
772
850
|
tokenUrl,
|
|
773
|
-
query: req.query
|
|
851
|
+
query: req.query,
|
|
852
|
+
proxyId: req.query.proxyId,
|
|
853
|
+
isFromMCP
|
|
774
854
|
});
|
|
775
855
|
if (userInfo) {
|
|
776
856
|
const jwtToken = jwt.generateJwt({
|
|
777
857
|
id: userInfo.id.toString(),
|
|
778
858
|
platform: platformName
|
|
779
859
|
});
|
|
780
|
-
|
|
781
|
-
|
|
860
|
+
// Store in session if sessionId exists (MCP flow)
|
|
861
|
+
if (isFromMCP) {
|
|
862
|
+
await updateAuthSession(sessionId, {
|
|
863
|
+
status: 'completed',
|
|
864
|
+
jwtToken,
|
|
865
|
+
userInfo: {
|
|
866
|
+
id: userInfo.id,
|
|
867
|
+
name: userInfo.name
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
res.status(200).send("Authentication successful. Please go back to AI Agent and confirm it.");
|
|
871
|
+
success = true;
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
|
|
875
|
+
success = true;
|
|
876
|
+
}
|
|
782
877
|
}
|
|
783
878
|
else {
|
|
784
879
|
res.status(200).send(tracer ? tracer.wrapResponse({ returnMessage }) : { returnMessage });
|
|
785
|
-
|
|
880
|
+
await updateAuthSession(sessionId, {
|
|
881
|
+
status: 'failed',
|
|
882
|
+
errorMessage: returnMessage?.message || 'Authentication failed'
|
|
883
|
+
});
|
|
786
884
|
}
|
|
885
|
+
success = false;
|
|
787
886
|
}
|
|
788
887
|
catch (e) {
|
|
789
|
-
|
|
888
|
+
logger.error('OAuth callback failed', { platform: platformName, stack: e.stack });
|
|
790
889
|
tracer?.traceError('oauth-callback:error', e, { platform: platformName });
|
|
791
890
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
891
|
+
if (sessionId) {
|
|
892
|
+
await updateAuthSession(sessionId, {
|
|
893
|
+
status: 'failed',
|
|
894
|
+
errorMessage: e.message || e.toString()
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
792
898
|
success = false;
|
|
793
899
|
}
|
|
794
900
|
const requestEndTime = new Date().getTime();
|
|
@@ -805,7 +911,7 @@ function createCoreRouter() {
|
|
|
805
911
|
author,
|
|
806
912
|
eventAddedVia
|
|
807
913
|
});
|
|
808
|
-
})
|
|
914
|
+
});
|
|
809
915
|
router.post('/apiKeyLogin', async function (req, res) {
|
|
810
916
|
const requestStartTime = new Date().getTime();
|
|
811
917
|
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
@@ -845,7 +951,7 @@ function createCoreRouter() {
|
|
|
845
951
|
}
|
|
846
952
|
}
|
|
847
953
|
catch (e) {
|
|
848
|
-
|
|
954
|
+
logger.error('API key login failed', { platform: platformName, stack: e.stack });
|
|
849
955
|
tracer?.traceError('apiKeyLogin:error', e, { platform: platformName });
|
|
850
956
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
851
957
|
success = false;
|
|
@@ -895,7 +1001,7 @@ function createCoreRouter() {
|
|
|
895
1001
|
}
|
|
896
1002
|
}
|
|
897
1003
|
catch (e) {
|
|
898
|
-
|
|
1004
|
+
logger.error('Unauthorize failed', { platform: platformName, stack: e.stack });
|
|
899
1005
|
tracer?.traceError('unAuthorize:error', e, { platform: platformName });
|
|
900
1006
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
901
1007
|
success = false;
|
|
@@ -924,7 +1030,7 @@ function createCoreRouter() {
|
|
|
924
1030
|
res.status(200).send(tracer ? tracer.wrapResponse({ extensionId, accountId }) : { extensionId, accountId });
|
|
925
1031
|
}
|
|
926
1032
|
catch (e) {
|
|
927
|
-
|
|
1033
|
+
logger.error('Get user info hash failed', { stack: e.stack });
|
|
928
1034
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
929
1035
|
tracer?.traceError('userInfoHash:error', e);
|
|
930
1036
|
}
|
|
@@ -978,7 +1084,7 @@ function createCoreRouter() {
|
|
|
978
1084
|
}
|
|
979
1085
|
}
|
|
980
1086
|
catch (e) {
|
|
981
|
-
|
|
1087
|
+
logger.error('Find contact failed', { platform: platformName, stack: e.stack });
|
|
982
1088
|
tracer?.traceError('findContact:error', e, { platform: platformName });
|
|
983
1089
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
984
1090
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1036,7 +1142,7 @@ function createCoreRouter() {
|
|
|
1036
1142
|
}
|
|
1037
1143
|
}
|
|
1038
1144
|
catch (e) {
|
|
1039
|
-
|
|
1145
|
+
logger.error('Create contact failed', { platform: platformName, stack: e.stack });
|
|
1040
1146
|
tracer?.traceError('createContact:error', e, { platform: platformName });
|
|
1041
1147
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1042
1148
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1079,7 +1185,7 @@ function createCoreRouter() {
|
|
|
1079
1185
|
}
|
|
1080
1186
|
const { id: userId, platform } = decodedToken;
|
|
1081
1187
|
platformName = platform;
|
|
1082
|
-
const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ sessionId: req.body.sessionId, note: req.body.note });
|
|
1188
|
+
const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ platform, userId, sessionId: req.body.sessionId, note: req.body.note });
|
|
1083
1189
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1084
1190
|
success = true;
|
|
1085
1191
|
if (extraDataTracking) {
|
|
@@ -1087,7 +1193,7 @@ function createCoreRouter() {
|
|
|
1087
1193
|
}
|
|
1088
1194
|
}
|
|
1089
1195
|
} catch (e) {
|
|
1090
|
-
|
|
1196
|
+
logger.error('Save note cache failed', { platform: platformName, stack: e.stack });
|
|
1091
1197
|
tracer?.traceError('saveNoteCache:error', e, { platform: platformName });
|
|
1092
1198
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1093
1199
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
@@ -1101,6 +1207,14 @@ function createCoreRouter() {
|
|
|
1101
1207
|
accountId: hashedAccountId,
|
|
1102
1208
|
extensionId: hashedExtensionId,
|
|
1103
1209
|
success,
|
|
1210
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
1211
|
+
userAgent,
|
|
1212
|
+
ip,
|
|
1213
|
+
author,
|
|
1214
|
+
eventAddedVia,
|
|
1215
|
+
extras: {
|
|
1216
|
+
...extraData
|
|
1217
|
+
}
|
|
1104
1218
|
});
|
|
1105
1219
|
})
|
|
1106
1220
|
router.get('/callLog', async function (req, res) {
|
|
@@ -1137,7 +1251,7 @@ function createCoreRouter() {
|
|
|
1137
1251
|
}
|
|
1138
1252
|
}
|
|
1139
1253
|
catch (e) {
|
|
1140
|
-
|
|
1254
|
+
logger.error('Get call log failed', { platform: platformName, stack: e.stack });
|
|
1141
1255
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1142
1256
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1143
1257
|
tracer?.traceError('getCallLog:error', e, { platform: platformName });
|
|
@@ -1194,7 +1308,7 @@ function createCoreRouter() {
|
|
|
1194
1308
|
}
|
|
1195
1309
|
}
|
|
1196
1310
|
catch (e) {
|
|
1197
|
-
|
|
1311
|
+
logger.error('Create call log failed', { platform: platformName, stack: e.stack });
|
|
1198
1312
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1199
1313
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1200
1314
|
tracer?.traceError('createCallLog:error', e, { platform: platformName });
|
|
@@ -1251,7 +1365,7 @@ function createCoreRouter() {
|
|
|
1251
1365
|
}
|
|
1252
1366
|
}
|
|
1253
1367
|
catch (e) {
|
|
1254
|
-
|
|
1368
|
+
logger.error('Update call log failed', { platform: platformName, stack: e.stack });
|
|
1255
1369
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1256
1370
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1257
1371
|
tracer?.traceError('updateCallLog:error', e, { platform: platformName });
|
|
@@ -1313,7 +1427,7 @@ function createCoreRouter() {
|
|
|
1313
1427
|
}
|
|
1314
1428
|
}
|
|
1315
1429
|
catch (e) {
|
|
1316
|
-
|
|
1430
|
+
logger.error('Upsert call disposition failed', { platform: platformName, stack: e.stack });
|
|
1317
1431
|
extraData.statusCode = e.response?.status ?? 'unknown';
|
|
1318
1432
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1319
1433
|
tracer?.traceError('upsertCallDisposition:error', e, { platform: platformName });
|
|
@@ -1371,7 +1485,7 @@ function createCoreRouter() {
|
|
|
1371
1485
|
}
|
|
1372
1486
|
}
|
|
1373
1487
|
catch (e) {
|
|
1374
|
-
|
|
1488
|
+
logger.error('Create message log failed', { platform: platformName, stack: e.stack });
|
|
1375
1489
|
statusCode = e.response?.status ?? 'unknown';
|
|
1376
1490
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1377
1491
|
tracer?.traceError('createMessageLog:error', e, { platform: platformName });
|
|
@@ -1411,11 +1525,16 @@ function createCoreRouter() {
|
|
|
1411
1525
|
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
1412
1526
|
return;
|
|
1413
1527
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1528
|
+
try {
|
|
1529
|
+
const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
|
|
1530
|
+
success = true;
|
|
1531
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, id }) : { successful: true, id });
|
|
1532
|
+
}
|
|
1533
|
+
catch (e) {
|
|
1534
|
+
return handleDatabaseError(e, 'Error scheduling call down');
|
|
1535
|
+
}
|
|
1417
1536
|
} catch (e) {
|
|
1418
|
-
|
|
1537
|
+
logger.error('Schedule call down failed', { platform: platformName, stack: e.stack });
|
|
1419
1538
|
statusCode = e.response?.status ?? 'unknown';
|
|
1420
1539
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1421
1540
|
tracer?.traceError('scheduleCallDown:error', e, { platform: platformName });
|
|
@@ -1458,7 +1577,7 @@ function createCoreRouter() {
|
|
|
1458
1577
|
success = true;
|
|
1459
1578
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, items }) : { successful: true, items });
|
|
1460
1579
|
} catch (e) {
|
|
1461
|
-
|
|
1580
|
+
logger.error('Get call down list failed', { platform: platformName, stack: e.stack });
|
|
1462
1581
|
statusCode = e.response?.status ?? 'unknown';
|
|
1463
1582
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1464
1583
|
tracer?.traceError('getCallDownList:error', e, { platform: platformName });
|
|
@@ -1506,7 +1625,7 @@ function createCoreRouter() {
|
|
|
1506
1625
|
success = true;
|
|
1507
1626
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
1508
1627
|
} catch (e) {
|
|
1509
|
-
|
|
1628
|
+
logger.error('Delete call down item failed', { platform: platformName, stack: e.stack });
|
|
1510
1629
|
statusCode = e.response?.status ?? 'unknown';
|
|
1511
1630
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1512
1631
|
tracer?.traceError('deleteCallDownItem:error', e, { platform: platformName });
|
|
@@ -1553,7 +1672,7 @@ function createCoreRouter() {
|
|
|
1553
1672
|
success = true;
|
|
1554
1673
|
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
1555
1674
|
} catch (e) {
|
|
1556
|
-
|
|
1675
|
+
logger.error('Mark call down called failed', { platform: platformName, stack: e.stack });
|
|
1557
1676
|
statusCode = e.response?.status ?? 'unknown';
|
|
1558
1677
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1559
1678
|
tracer?.traceError('markCallDownCalled:error', e, { platform: platformName });
|
|
@@ -1581,7 +1700,6 @@ function createCoreRouter() {
|
|
|
1581
1700
|
tracer?.trace('contactSearchByName:start', { query: req.query });
|
|
1582
1701
|
let platformName = null;
|
|
1583
1702
|
let success = false;
|
|
1584
|
-
let resultCount = 0;
|
|
1585
1703
|
let statusCode = 200;
|
|
1586
1704
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1587
1705
|
try {
|
|
@@ -1601,7 +1719,7 @@ function createCoreRouter() {
|
|
|
1601
1719
|
|
|
1602
1720
|
}
|
|
1603
1721
|
catch (e) {
|
|
1604
|
-
|
|
1722
|
+
logger.error('Contact search by name failed', { platform: platformName, stack: e.stack });
|
|
1605
1723
|
statusCode = e.response?.status ?? 'unknown';
|
|
1606
1724
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1607
1725
|
tracer?.traceError('contactSearchByName:error', e, { platform: platformName });
|
|
@@ -1651,7 +1769,7 @@ function createCoreRouter() {
|
|
|
1651
1769
|
success = false;
|
|
1652
1770
|
}
|
|
1653
1771
|
catch (e) {
|
|
1654
|
-
|
|
1772
|
+
logger.error('Get admin report failed', { stack: e.stack });
|
|
1655
1773
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1656
1774
|
tracer?.traceError('getAdminReport:error', e, { platform: platformName });
|
|
1657
1775
|
}
|
|
@@ -1696,7 +1814,7 @@ function createCoreRouter() {
|
|
|
1696
1814
|
success = false;
|
|
1697
1815
|
}
|
|
1698
1816
|
catch (e) {
|
|
1699
|
-
|
|
1817
|
+
logger.error('Get user report failed', { stack: e.stack });
|
|
1700
1818
|
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1701
1819
|
tracer?.traceError('getUserReport:error', e, { platform: platformName });
|
|
1702
1820
|
}
|
|
@@ -1822,6 +1940,121 @@ function createCoreRouter() {
|
|
|
1822
1940
|
}
|
|
1823
1941
|
});
|
|
1824
1942
|
}
|
|
1943
|
+
// For chatGPT verification
|
|
1944
|
+
router.get('/.well-known/openai-apps-challenge', (req, res) => {
|
|
1945
|
+
res.send(process.env.CHATGPT_VERIFICATION_CODE);
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
// --- METADATA ENDPOINT 1: Resource Metadata ---
|
|
1949
|
+
// Tells the client "I am protected" and "Here is who protects me"
|
|
1950
|
+
router.get('/.well-known/oauth-protected-resource', (req, res) => {
|
|
1951
|
+
res.json({
|
|
1952
|
+
resource: process.env.APP_SERVER,
|
|
1953
|
+
// CHANGE THIS: Point to your own server so the client fetches YOUR metadata next
|
|
1954
|
+
authorization_servers: [process.env.APP_SERVER],
|
|
1955
|
+
scopes_supported: ["ReadAccounts"]
|
|
1956
|
+
});
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
// --- METADATA ENDPOINT 2: Auth Server Metadata ---
|
|
1960
|
+
// Usually, you can redirect to your provider's configuration.
|
|
1961
|
+
// If your provider supports OIDC discovery, this is often sufficient.
|
|
1962
|
+
router.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
1963
|
+
res.json({
|
|
1964
|
+
issuer: process.env.APP_SERVER,
|
|
1965
|
+
registration_endpoint: `${process.env.APP_SERVER}/oauth/register`,
|
|
1966
|
+
|
|
1967
|
+
// CHANGE THIS: Don't point to RingCentral. Point to your own Shim.
|
|
1968
|
+
authorization_endpoint: `${process.env.APP_SERVER}/oauth/authorize_shim`,
|
|
1969
|
+
|
|
1970
|
+
// Keep the token endpoint pointing to RingCentral (that usually works fine)
|
|
1971
|
+
token_endpoint: `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/token`,
|
|
1972
|
+
token_endpoint_auth_methods_supported: ["client_secret_basic"],
|
|
1973
|
+
response_types_supported: ["code"]
|
|
1974
|
+
});
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
router.get('/oauth/authorize_shim', (req, res) => {
|
|
1978
|
+
// 1. Get the parameters ChatGPT sent us
|
|
1979
|
+
const { response_type, client_id, redirect_uri, state, scope } = req.query;
|
|
1980
|
+
|
|
1981
|
+
// 2. Rebuild the query string for RingCentral
|
|
1982
|
+
// We explicitly LEAVE OUT 'resource' and any other junk
|
|
1983
|
+
const params = new URLSearchParams({
|
|
1984
|
+
response_type,
|
|
1985
|
+
client_id, // This will be your RC Client ID (since ChatGPT got it from /register)
|
|
1986
|
+
redirect_uri,
|
|
1987
|
+
state,
|
|
1988
|
+
scope
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
// 3. Redirect the user's browser to the REAL RingCentral URL
|
|
1992
|
+
const rcUrl = `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/authorize?${params.toString()}`;
|
|
1993
|
+
|
|
1994
|
+
console.log("Proxying OAuth request to:", rcUrl); // Helpful for debugging
|
|
1995
|
+
res.redirect(rcUrl);
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
router.post('/oauth/register', (req, res) => {
|
|
1999
|
+
// The MCP client calls this to get credentials.
|
|
2000
|
+
// We simply return our hardcoded RingCentral app credentials.
|
|
2001
|
+
res.json({
|
|
2002
|
+
client_id: process.env.RINGCENTRAL_CLIENT_ID,
|
|
2003
|
+
client_secret: process.env.RINGCENTRAL_CLIENT_SECRET
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
router.use('/mcp', (req, res, next) => {// LOG EVERYTHING
|
|
2008
|
+
console.log(`[${req.method}] /mcp`);
|
|
2009
|
+
console.log("Headers:", JSON.stringify(req.headers['authorization'] ? "Auth Token Present" : "No Auth"));
|
|
2010
|
+
console.log("Body:", JSON.stringify(req.body));
|
|
2011
|
+
// return next();
|
|
2012
|
+
// Capture the response finish to see the status code
|
|
2013
|
+
res.on('finish', () => {
|
|
2014
|
+
console.log(`[Response] Status: ${res.statusCode}`);
|
|
2015
|
+
console.log(`[Response] data: ${JSON.stringify(res.data)}`);
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
const authHeader = req.headers.authorization;
|
|
2019
|
+
const token = authHeader?.split(' ')[1]; // Remove "Bearer "
|
|
2020
|
+
// Allow the initial connection (GET) and CORS checks (OPTIONS) to pass freely.
|
|
2021
|
+
// We only want to block the actual commands (POST).
|
|
2022
|
+
if (req.method === 'GET' || req.method === 'OPTIONS') {
|
|
2023
|
+
return next();
|
|
2024
|
+
}
|
|
2025
|
+
// SCENARIO 1: No Token provided. Kick off the OAuth flow.
|
|
2026
|
+
if (!token) {
|
|
2027
|
+
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
2028
|
+
return res.status(401).send();
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// SCENARIO 2: Token provided. Verify it.
|
|
2032
|
+
try {
|
|
2033
|
+
next();
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
console.error("Token validation failed:", error.message);
|
|
2036
|
+
// Token is invalid or expired
|
|
2037
|
+
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
2038
|
+
return res.status(401).send();
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
// Handle OPTIONS for CORS preflight
|
|
2042
|
+
router.options('/mcp', (req, res) => {
|
|
2043
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2044
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
2045
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
2046
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
2047
|
+
res.status(200).end();
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
// Dedicated endpoint for all MCP traffic
|
|
2051
|
+
router.post('/mcp', async (req, res) => {
|
|
2052
|
+
// Set CORS headers
|
|
2053
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2054
|
+
res.setHeader('Content-Type', 'application/json');
|
|
2055
|
+
|
|
2056
|
+
await mcpHandler.handleMcpRequest(req, res);
|
|
2057
|
+
});
|
|
1825
2058
|
|
|
1826
2059
|
return router;
|
|
1827
2060
|
}
|
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
|
}
|