@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.
Files changed (59) hide show
  1. package/connector/developerPortal.js +43 -0
  2. package/connector/proxy/index.js +10 -3
  3. package/connector/registry.js +8 -6
  4. package/handlers/admin.js +135 -22
  5. package/handlers/auth.js +89 -67
  6. package/handlers/calldown.js +10 -4
  7. package/handlers/contact.js +4 -104
  8. package/handlers/disposition.js +7 -145
  9. package/handlers/log.js +174 -258
  10. package/handlers/user.js +19 -6
  11. package/index.js +280 -47
  12. package/lib/analytics.js +3 -1
  13. package/lib/authSession.js +68 -0
  14. package/lib/callLogComposer.js +498 -420
  15. package/lib/errorHandler.js +206 -0
  16. package/lib/jwt.js +2 -0
  17. package/lib/logger.js +190 -0
  18. package/lib/oauth.js +21 -10
  19. package/lib/ringcentral.js +2 -10
  20. package/lib/sharedSMSComposer.js +471 -0
  21. package/mcp/SupportedPlatforms.md +12 -0
  22. package/mcp/lib/validator.js +91 -0
  23. package/mcp/mcpHandler.js +166 -0
  24. package/mcp/tools/checkAuthStatus.js +110 -0
  25. package/mcp/tools/collectAuthInfo.js +91 -0
  26. package/mcp/tools/createCallLog.js +308 -0
  27. package/mcp/tools/createContact.js +117 -0
  28. package/mcp/tools/createMessageLog.js +283 -0
  29. package/mcp/tools/doAuth.js +190 -0
  30. package/mcp/tools/findContactByName.js +92 -0
  31. package/mcp/tools/findContactByPhone.js +101 -0
  32. package/mcp/tools/getCallLog.js +98 -0
  33. package/mcp/tools/getGoogleFilePicker.js +103 -0
  34. package/mcp/tools/getHelp.js +44 -0
  35. package/mcp/tools/getPublicConnectors.js +53 -0
  36. package/mcp/tools/index.js +64 -0
  37. package/mcp/tools/logout.js +68 -0
  38. package/mcp/tools/rcGetCallLogs.js +78 -0
  39. package/mcp/tools/setConnector.js +69 -0
  40. package/mcp/tools/updateCallLog.js +122 -0
  41. package/models/cacheModel.js +3 -0
  42. package/package.json +71 -70
  43. package/releaseNotes.json +24 -0
  44. package/test/handlers/log.test.js +11 -4
  45. package/test/lib/logger.test.js +206 -0
  46. package/test/lib/ringcentral.test.js +0 -6
  47. package/test/lib/sharedSMSComposer.test.js +1084 -0
  48. package/test/mcp/tools/collectAuthInfo.test.js +234 -0
  49. package/test/mcp/tools/createCallLog.test.js +425 -0
  50. package/test/mcp/tools/createMessageLog.test.js +580 -0
  51. package/test/mcp/tools/doAuth.test.js +376 -0
  52. package/test/mcp/tools/findContactByName.test.js +263 -0
  53. package/test/mcp/tools/findContactByPhone.test.js +284 -0
  54. package/test/mcp/tools/getCallLog.test.js +286 -0
  55. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -0
  56. package/test/mcp/tools/getPublicConnectors.test.js +128 -0
  57. package/test/mcp/tools/logout.test.js +169 -0
  58. package/test/mcp/tools/setConnector.test.js +177 -0
  59. 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
- console.log('creating db tables if not exist...');
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`${e.stack}`);
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
- tracer?.trace('oauth-callback:missingCallbackUri', {});
751
- res.status(400).send(tracer ? tracer.wrapResponse('Missing callbackUri') : 'Missing callbackUri');
752
- return;
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
- platformName = req.query.state ?
755
- req.query.state.split('platform=')[1] :
756
- decodeURIComponent(decodeURIComponent(req.originalUrl).split('state=')[1].split('&')[0]).split('platform=')[1];
757
- const hostname = req.query.hostname;
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
- res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
781
- success = true;
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
- success = false;
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
1415
- success = true;
1416
- res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, id }) : { successful: true, id });
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`platform: ${platformName} \n${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`${e.stack}`);
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
- console.log(`Event: ${eventName}`);
54
+ logger.info(`Event: ${eventName}`);
53
55
  }