@app-connect/core 1.7.16 → 1.7.18

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/lib/oauth.js CHANGED
@@ -101,41 +101,48 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
101
101
  try {
102
102
  const startRefreshTime = moment();
103
103
  const token = oauthApp.createToken(user.accessToken, user.refreshToken);
104
- logger.info('token refreshing...')
104
+ logger.info('token refreshing...')
105
105
  const { accessToken, refreshToken, expires } = await token.refresh();
106
106
  user.accessToken = accessToken;
107
107
  user.refreshToken = refreshToken;
108
108
  user.tokenExpiry = expires;
109
- try {
110
- await user.save();
111
- }
112
- catch (error) {
113
- return handleDatabaseError(error, 'Error saving user');
114
- }
109
+ try {
110
+ await user.save();
111
+ }
112
+ catch (error) {
113
+ return handleDatabaseError(error, 'Error saving user');
114
+ }
115
115
  if (newLock) {
116
116
  const deletionStartTime = moment();
117
117
  await newLock.delete();
118
118
  const deletionEndTime = moment();
119
- logger.info(`lock deleted in ${deletionEndTime.diff(deletionStartTime)}ms`)
119
+ logger.info(`lock deleted in ${deletionEndTime.diff(deletionStartTime)}ms`)
120
120
  }
121
121
  const endRefreshTime = moment();
122
- logger.info(`token refreshing finished in ${endRefreshTime.diff(startRefreshTime)}ms`)
122
+ logger.info(`token refreshing finished in ${endRefreshTime.diff(startRefreshTime)}ms`)
123
123
  }
124
124
  catch (e) {
125
125
  console.log('token refreshing failed', e.stack)
126
126
  if (newLock) {
127
127
  await newLock.delete();
128
128
  }
129
+ return null;
129
130
  }
130
131
  }
131
132
  // case: run withou token refresh lock
132
133
  else {
133
- logger.info('token refreshing...')
134
- const token = oauthApp.createToken(user.accessToken, user.refreshToken);
135
- const { accessToken, refreshToken, expires } = await token.refresh();
136
- user.accessToken = accessToken;
137
- user.refreshToken = refreshToken;
138
- user.tokenExpiry = expires;
134
+ try {
135
+ logger.info('token refreshing...')
136
+ const token = oauthApp.createToken(user.accessToken, user.refreshToken);
137
+ const { accessToken, refreshToken, expires } = await token.refresh();
138
+ user.accessToken = accessToken;
139
+ user.refreshToken = refreshToken;
140
+ user.tokenExpiry = expires;
141
+ }
142
+ catch (e) {
143
+ console.log('token refreshing failed', e.stack)
144
+ return null;
145
+ }
139
146
  try {
140
147
  await user.save();
141
148
  }
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@app-connect/core",
3
- "version": "1.7.16",
4
- "description": "RingCentral App Connect Core",
5
- "main": "index.js",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
- },
10
- "keywords": [
11
- "RingCentral",
12
- "App Connect"
13
- ],
14
- "author": "RingCentral Labs",
15
- "license": "MIT",
16
- "peerDependencies": {
17
- "axios": "^1.12.2",
18
- "express": "^4.22.1",
19
- "moment": "^2.29.4",
20
- "moment-timezone": "^0.5.39",
21
- "pg": "^8.8.0",
22
- "sequelize": "^6.29.0"
23
- },
24
- "dependencies": {
25
- "@aws-sdk/client-dynamodb": "^3.751.0",
26
- "@aws-sdk/client-s3": "^3.947.0",
27
- "@aws-sdk/s3-request-presigner": "^3.947.0",
28
- "@modelcontextprotocol/sdk": "^1.21.1",
29
- "awesome-phonenumber": "^5.6.0",
30
- "body-parser": "^1.20.4",
31
- "body-parser-xml": "^2.0.5",
32
- "client-oauth2": "^4.3.3",
33
- "cors": "^2.8.5",
34
- "country-state-city": "^3.2.1",
35
- "dotenv": "^16.0.3",
36
- "dynamoose": "^4.0.3",
37
- "jsonwebtoken": "^9.0.0",
38
- "mixpanel": "^0.18.0",
39
- "shortid": "^2.2.17",
40
- "tz-lookup": "^6.1.25",
41
- "ua-parser-js": "^1.0.38"
42
- },
43
- "scripts": {
44
- "test": "jest",
45
- "test:watch": "jest --watch",
46
- "test:coverage": "jest --coverage",
47
- "test:ci": "jest --ci --coverage --watchAll=false"
48
- },
49
- "devDependencies": {
50
- "@eslint/js": "^9.22.0",
51
- "@octokit/rest": "^19.0.5",
52
- "axios": "^1.12.2",
53
- "eslint": "^9.22.0",
54
- "express": "^4.22.1",
55
- "globals": "^16.0.0",
56
- "jest": "^29.3.1",
57
- "moment": "^2.29.4",
58
- "moment-timezone": "^0.5.39",
59
- "nock": "^13.2.9",
60
- "pg": "^8.8.0",
61
- "sequelize": "^6.29.0",
62
- "sqlite3": "^5.1.2",
63
- "supertest": "^6.3.1"
64
- },
65
- "overrides": {
66
- "js-object-utilities": "2.2.1"
67
- },
68
- "bugs": {
69
- "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
- },
71
- "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
- }
1
+ {
2
+ "name": "@app-connect/core",
3
+ "version": "1.7.18",
4
+ "description": "RingCentral App Connect Core",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
+ },
10
+ "keywords": [
11
+ "RingCentral",
12
+ "App Connect"
13
+ ],
14
+ "author": "RingCentral Labs",
15
+ "license": "MIT",
16
+ "peerDependencies": {
17
+ "axios": "^1.12.2",
18
+ "express": "^4.22.1",
19
+ "moment": "^2.29.4",
20
+ "moment-timezone": "^0.5.39",
21
+ "pg": "^8.8.0",
22
+ "sequelize": "^6.29.0"
23
+ },
24
+ "dependencies": {
25
+ "@aws-sdk/client-dynamodb": "^3.751.0",
26
+ "@aws-sdk/client-s3": "^3.947.0",
27
+ "@aws-sdk/s3-request-presigner": "^3.947.0",
28
+ "@modelcontextprotocol/sdk": "^1.21.1",
29
+ "awesome-phonenumber": "^5.6.0",
30
+ "body-parser": "^1.20.4",
31
+ "body-parser-xml": "^2.0.5",
32
+ "client-oauth2": "^4.3.3",
33
+ "cors": "^2.8.5",
34
+ "country-state-city": "^3.2.1",
35
+ "dotenv": "^16.0.3",
36
+ "dynamoose": "^4.0.3",
37
+ "jsonwebtoken": "^9.0.0",
38
+ "mixpanel": "^0.18.0",
39
+ "shortid": "^2.2.17",
40
+ "tz-lookup": "^6.1.25",
41
+ "ua-parser-js": "^1.0.38"
42
+ },
43
+ "scripts": {
44
+ "test": "jest",
45
+ "test:watch": "jest --watch",
46
+ "test:coverage": "jest --coverage",
47
+ "test:ci": "jest --ci --coverage --watchAll=false"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.22.0",
51
+ "@octokit/rest": "^19.0.5",
52
+ "axios": "^1.12.2",
53
+ "eslint": "^9.22.0",
54
+ "express": "^4.22.1",
55
+ "globals": "^16.0.0",
56
+ "jest": "^29.3.1",
57
+ "moment": "^2.29.4",
58
+ "moment-timezone": "^0.5.39",
59
+ "nock": "^13.2.9",
60
+ "pg": "^8.8.0",
61
+ "sequelize": "^6.29.0",
62
+ "sqlite3": "^5.1.2",
63
+ "supertest": "^6.3.1"
64
+ },
65
+ "overrides": {
66
+ "js-object-utilities": "2.2.1"
67
+ },
68
+ "bugs": {
69
+ "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
+ },
71
+ "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
+ }
package/releaseNotes.json CHANGED
@@ -1,4 +1,20 @@
1
1
  {
2
+ "1.7.18": {
3
+ "global": [
4
+ {
5
+ "type": "New",
6
+ "description": "Support for Group SMS logging"
7
+ },
8
+ {
9
+ "type": "Better",
10
+ "description": "User session will log be revoked is token refresh fails"
11
+ },
12
+ {
13
+ "type": "Fix",
14
+ "description": "Contact call pop issue"
15
+ }
16
+ ]
17
+ },
2
18
  "1.7.16": {
3
19
  "global": [
4
20
  {
@@ -29,6 +29,7 @@ const logHandler = require('../../handlers/log');
29
29
  const { CallLogModel } = require('../../models/callLogModel');
30
30
  const { MessageLogModel } = require('../../models/messageLogModel');
31
31
  const { UserModel } = require('../../models/userModel');
32
+ const { AccountDataModel } = require('../../models/accountDataModel');
32
33
  const connectorRegistry = require('../../connector/registry');
33
34
  const oauth = require('../../lib/oauth');
34
35
  const { composeCallLog } = require('../../lib/callLogComposer');
@@ -40,12 +41,14 @@ describe('Log Handler', () => {
40
41
  await CallLogModel.sync({ force: true });
41
42
  await MessageLogModel.sync({ force: true });
42
43
  await UserModel.sync({ force: true });
44
+ await AccountDataModel.sync({ force: true });
43
45
  });
44
46
 
45
47
  afterEach(async () => {
46
48
  await CallLogModel.destroy({ where: {} });
47
49
  await MessageLogModel.destroy({ where: {} });
48
50
  await UserModel.destroy({ where: {} });
51
+ await AccountDataModel.destroy({ where: {} });
49
52
  jest.clearAllMocks();
50
53
  });
51
54
 
@@ -742,6 +745,228 @@ describe('Log Handler', () => {
742
745
  expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
743
746
  expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
744
747
  });
748
+
749
+ test('should handle group SMS with contactId suffix for message IDs', async () => {
750
+ // Arrange - group SMS has multiple correspondents
751
+ await UserModel.create({
752
+ id: 'test-user-id',
753
+ platform: 'testCRM',
754
+ accessToken: 'test-token',
755
+ rcAccountId: 'rc-account-123',
756
+ platformAdditionalInfo: {}
757
+ });
758
+
759
+ const mockConnector = {
760
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
761
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
762
+ createMessageLog: jest.fn().mockResolvedValue({
763
+ logId: 'msg-log-group-123',
764
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
765
+ }),
766
+ updateMessageLog: jest.fn()
767
+ };
768
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
769
+
770
+ const incomingData = {
771
+ logInfo: {
772
+ messages: [{ id: 'msg-group-1', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
773
+ correspondents: [
774
+ { phoneNumber: '+1234567890' },
775
+ { phoneNumber: '+0987654321' }
776
+ ],
777
+ conversationId: 'conv-group-123',
778
+ conversationLogId: 'conv-log-group-123'
779
+ },
780
+ contactId: 'contact-456',
781
+ contactType: 'Contact',
782
+ contactName: 'Primary Contact',
783
+ additionalSubmission: {}
784
+ };
785
+
786
+ // Act
787
+ const result = await logHandler.createMessageLog({
788
+ platform: 'testCRM',
789
+ userId: 'test-user-id',
790
+ incomingData
791
+ });
792
+
793
+ // Assert
794
+ expect(result.successful).toBe(true);
795
+ expect(result.logIds).toContain('msg-group-1-contact-456');
796
+ const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-group-1-contact-456' } });
797
+ expect(savedLog).not.toBeNull();
798
+ expect(savedLog.thirdPartyLogId).toBe('msg-log-group-123');
799
+ });
800
+
801
+ test('should pass correspondents to createMessageLog when group SMS has different contact names', async () => {
802
+ // Arrange - correspondent in cache with different name
803
+ await UserModel.create({
804
+ id: 'test-user-id',
805
+ platform: 'testCRM',
806
+ accessToken: 'test-token',
807
+ rcAccountId: 'rc-account-123',
808
+ platformAdditionalInfo: {}
809
+ });
810
+
811
+ await AccountDataModel.create({
812
+ rcAccountId: 'rc-account-123',
813
+ platformName: 'testCRM',
814
+ dataKey: 'contact-+0987654321',
815
+ data: [{ name: 'Other Contact', id: 'contact-789' }]
816
+ });
817
+
818
+ const mockConnector = {
819
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
820
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
821
+ createMessageLog: jest.fn().mockResolvedValue({
822
+ logId: 'msg-log-correspondents',
823
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
824
+ }),
825
+ updateMessageLog: jest.fn()
826
+ };
827
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
828
+
829
+ const incomingData = {
830
+ logInfo: {
831
+ messages: [{ id: 'msg-correspondents', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
832
+ correspondents: [
833
+ { phoneNumber: '+1234567890' },
834
+ { phoneNumber: '+0987654321' }
835
+ ],
836
+ conversationId: 'conv-correspondents',
837
+ conversationLogId: 'conv-log-correspondents'
838
+ },
839
+ contactId: 'contact-456',
840
+ contactType: 'Contact',
841
+ contactName: 'Primary Contact',
842
+ additionalSubmission: {}
843
+ };
844
+
845
+ // Act
846
+ await logHandler.createMessageLog({
847
+ platform: 'testCRM',
848
+ userId: 'test-user-id',
849
+ incomingData
850
+ });
851
+
852
+ // Assert - createMessageLog should receive correspondents with different name
853
+ expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
854
+ expect.objectContaining({
855
+ correspondents: [[{ name: 'Other Contact', id: 'contact-789' }]]
856
+ })
857
+ );
858
+ });
859
+
860
+ test('should not add correspondent when name matches contactName in group SMS', async () => {
861
+ // Arrange - correspondent in cache with same name as contactName
862
+ await UserModel.create({
863
+ id: 'test-user-id',
864
+ platform: 'testCRM',
865
+ accessToken: 'test-token',
866
+ rcAccountId: 'rc-account-123',
867
+ platformAdditionalInfo: {}
868
+ });
869
+
870
+ await AccountDataModel.create({
871
+ rcAccountId: 'rc-account-123',
872
+ platformName: 'testCRM',
873
+ dataKey: 'contact-+0987654321',
874
+ data: [{ name: 'Primary Contact', id: 'contact-789' }]
875
+ });
876
+
877
+ const mockConnector = {
878
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
879
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
880
+ createMessageLog: jest.fn().mockResolvedValue({
881
+ logId: 'msg-log-same-name',
882
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
883
+ }),
884
+ updateMessageLog: jest.fn()
885
+ };
886
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
887
+
888
+ const incomingData = {
889
+ logInfo: {
890
+ messages: [{ id: 'msg-same-name', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
891
+ correspondents: [
892
+ { phoneNumber: '+1234567890' },
893
+ { phoneNumber: '+0987654321' }
894
+ ],
895
+ conversationId: 'conv-same-name',
896
+ conversationLogId: 'conv-log-same-name'
897
+ },
898
+ contactId: 'contact-456',
899
+ contactType: 'Contact',
900
+ contactName: 'Primary Contact',
901
+ additionalSubmission: {}
902
+ };
903
+
904
+ // Act
905
+ await logHandler.createMessageLog({
906
+ platform: 'testCRM',
907
+ userId: 'test-user-id',
908
+ incomingData
909
+ });
910
+
911
+ // Assert - correspondents should be empty when names match
912
+ expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
913
+ expect.objectContaining({
914
+ correspondents: []
915
+ })
916
+ );
917
+ });
918
+
919
+ test('should use suffixed conversationLogId and conversationId for group SMS', async () => {
920
+ // Arrange
921
+ await UserModel.create({
922
+ id: 'test-user-id',
923
+ platform: 'testCRM',
924
+ accessToken: 'test-token',
925
+ rcAccountId: 'rc-account-123',
926
+ platformAdditionalInfo: {}
927
+ });
928
+
929
+ const mockConnector = {
930
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
931
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
932
+ createMessageLog: jest.fn().mockResolvedValue({
933
+ logId: 'msg-log-suffix',
934
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
935
+ }),
936
+ updateMessageLog: jest.fn()
937
+ };
938
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
939
+
940
+ const incomingData = {
941
+ logInfo: {
942
+ messages: [{ id: 'msg-suffix', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
943
+ correspondents: [
944
+ { phoneNumber: '+1234567890' },
945
+ { phoneNumber: '+0987654321' }
946
+ ],
947
+ conversationId: 'conv-original',
948
+ conversationLogId: 'conv-log-original'
949
+ },
950
+ contactId: 'contact-999',
951
+ contactType: 'Contact',
952
+ contactName: 'Test Contact',
953
+ additionalSubmission: {}
954
+ };
955
+
956
+ // Act
957
+ const result = await logHandler.createMessageLog({
958
+ platform: 'testCRM',
959
+ userId: 'test-user-id',
960
+ incomingData
961
+ });
962
+
963
+ // Assert - message log saved with suffixed conversationLogId
964
+ expect(result.successful).toBe(true);
965
+ const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-suffix-contact-999' } });
966
+ expect(savedLog).not.toBeNull();
967
+ expect(savedLog.conversationLogId).toBe('conv-log-original-contact-999');
968
+ expect(savedLog.conversationId).toBe('conv-original-contact-999');
969
+ });
745
970
  });
746
971
 
747
972
  describe('saveNoteCache', () => {