@app-connect/core 1.7.25 → 1.7.27

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 (138) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -31
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -164
  6. package/connector/proxy/index.js +500 -500
  7. package/connector/registry.js +252 -252
  8. package/docs/README.md +50 -50
  9. package/docs/architecture.md +93 -93
  10. package/docs/connectors.md +116 -116
  11. package/docs/handlers.md +125 -125
  12. package/docs/libraries.md +101 -101
  13. package/docs/models.md +144 -144
  14. package/docs/routes.md +115 -115
  15. package/docs/tests.md +73 -73
  16. package/handlers/admin.js +523 -523
  17. package/handlers/appointment.js +193 -0
  18. package/handlers/auth.js +320 -296
  19. package/handlers/calldown.js +99 -99
  20. package/handlers/contact.js +280 -280
  21. package/handlers/disposition.js +82 -80
  22. package/handlers/log.js +990 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/managedOAuth.js +247 -0
  25. package/handlers/plugin.js +208 -208
  26. package/handlers/user.js +142 -142
  27. package/index.js +3162 -2652
  28. package/jest.config.js +56 -56
  29. package/lib/analytics.js +54 -54
  30. package/lib/authSession.js +109 -109
  31. package/lib/cacheCleanup.js +21 -0
  32. package/lib/callLogComposer.js +898 -898
  33. package/lib/callLogLookup.js +34 -0
  34. package/lib/constants.js +8 -8
  35. package/lib/debugTracer.js +177 -177
  36. package/lib/encode.js +30 -30
  37. package/lib/errorHandler.js +218 -206
  38. package/lib/generalErrorMessage.js +41 -41
  39. package/lib/jwt.js +18 -18
  40. package/lib/logger.js +190 -190
  41. package/lib/migrateCallLogsSchema.js +116 -0
  42. package/lib/ringcentral.js +266 -266
  43. package/lib/s3ErrorLogReport.js +65 -65
  44. package/lib/sharedSMSComposer.js +471 -471
  45. package/lib/util.js +67 -67
  46. package/mcp/README.md +412 -395
  47. package/mcp/lib/validator.js +91 -91
  48. package/mcp/mcpHandler.js +425 -425
  49. package/mcp/tools/cancelAppointment.js +101 -0
  50. package/mcp/tools/checkAuthStatus.js +105 -105
  51. package/mcp/tools/confirmAppointment.js +101 -0
  52. package/mcp/tools/createAppointment.js +157 -0
  53. package/mcp/tools/createCallLog.js +327 -316
  54. package/mcp/tools/createContact.js +117 -117
  55. package/mcp/tools/createMessageLog.js +287 -287
  56. package/mcp/tools/doAuth.js +60 -60
  57. package/mcp/tools/findContactByName.js +93 -93
  58. package/mcp/tools/findContactByPhone.js +101 -101
  59. package/mcp/tools/getCallLog.js +111 -102
  60. package/mcp/tools/getGoogleFilePicker.js +99 -99
  61. package/mcp/tools/getHelp.js +43 -43
  62. package/mcp/tools/getPublicConnectors.js +94 -94
  63. package/mcp/tools/getSessionInfo.js +90 -90
  64. package/mcp/tools/index.js +51 -41
  65. package/mcp/tools/listAppointments.js +163 -0
  66. package/mcp/tools/logout.js +99 -96
  67. package/mcp/tools/rcGetCallLogs.js +65 -65
  68. package/mcp/tools/updateAppointment.js +154 -0
  69. package/mcp/tools/updateCallLog.js +130 -126
  70. package/mcp/ui/App/App.tsx +358 -358
  71. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  72. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  73. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  74. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  75. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  76. package/mcp/ui/App/lib/callTool.ts +130 -130
  77. package/mcp/ui/App/lib/debugLog.ts +41 -41
  78. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  79. package/mcp/ui/App/main.css +5 -5
  80. package/mcp/ui/App/root.tsx +13 -13
  81. package/mcp/ui/index.html +13 -13
  82. package/mcp/ui/package-lock.json +6356 -6356
  83. package/mcp/ui/package.json +25 -25
  84. package/mcp/ui/tsconfig.json +26 -26
  85. package/mcp/ui/vite.config.ts +16 -16
  86. package/models/accountDataModel.js +33 -33
  87. package/models/adminConfigModel.js +35 -35
  88. package/models/cacheModel.js +30 -26
  89. package/models/callDownListModel.js +34 -34
  90. package/models/callLogModel.js +33 -27
  91. package/models/dynamo/connectorSchema.js +146 -146
  92. package/models/dynamo/lockSchema.js +24 -24
  93. package/models/dynamo/noteCacheSchema.js +29 -29
  94. package/models/llmSessionModel.js +17 -17
  95. package/models/messageLogModel.js +25 -25
  96. package/models/sequelize.js +16 -16
  97. package/models/userModel.js +45 -45
  98. package/package.json +1 -1
  99. package/releaseNotes.json +1101 -1081
  100. package/test/connector/proxy/engine.test.js +126 -126
  101. package/test/connector/proxy/index.test.js +279 -279
  102. package/test/connector/proxy/sample.json +161 -161
  103. package/test/connector/registry.test.js +415 -415
  104. package/test/handlers/admin.test.js +616 -616
  105. package/test/handlers/auth.test.js +1092 -1018
  106. package/test/handlers/contact.test.js +1014 -1014
  107. package/test/handlers/log.test.js +1298 -1160
  108. package/test/handlers/managedAuth.test.js +457 -457
  109. package/test/handlers/plugin.test.js +380 -380
  110. package/test/index.test.js +105 -105
  111. package/test/lib/cacheCleanup.test.js +42 -0
  112. package/test/lib/callLogComposer.test.js +1231 -1231
  113. package/test/lib/debugTracer.test.js +328 -328
  114. package/test/lib/jwt.test.js +176 -176
  115. package/test/lib/logger.test.js +206 -206
  116. package/test/lib/oauth.test.js +359 -359
  117. package/test/lib/ringcentral.test.js +467 -467
  118. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  119. package/test/lib/util.test.js +329 -329
  120. package/test/mcp/tools/checkAuthStatus.test.js +83 -83
  121. package/test/mcp/tools/createCallLog.test.js +436 -436
  122. package/test/mcp/tools/createContact.test.js +58 -58
  123. package/test/mcp/tools/createMessageLog.test.js +595 -595
  124. package/test/mcp/tools/doAuth.test.js +113 -113
  125. package/test/mcp/tools/findContactByName.test.js +275 -275
  126. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  127. package/test/mcp/tools/getCallLog.test.js +298 -298
  128. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  129. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  130. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  131. package/test/mcp/tools/logout.test.js +233 -233
  132. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  133. package/test/mcp/tools/updateCallLog.test.js +360 -360
  134. package/test/models/accountDataModel.test.js +98 -98
  135. package/test/models/dynamo/connectorSchema.test.js +189 -189
  136. package/test/models/models.test.js +568 -539
  137. package/test/routes/managedAuthRoutes.test.js +235 -104
  138. package/test/setup.js +178 -178
@@ -0,0 +1,247 @@
1
+ const { AccountDataModel } = require('../models/accountDataModel');
2
+ const { CacheModel } = require('../models/cacheModel');
3
+ const { encode, decoded } = require('../lib/encode');
4
+
5
+ const MANAGED_OAUTH_ACCOUNT_DATA_KEY = 'managed-oauth-account';
6
+ const MANAGED_OAUTH_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const MANAGED_OAUTH_FIELDS = [
8
+ 'clientId',
9
+ 'clientSecret',
10
+ 'accessTokenUri',
11
+ 'authorizationUri',
12
+ 'redirectUri',
13
+ 'scopes',
14
+ 'hostname'
15
+ ];
16
+
17
+ function getPendingManagedOAuthCacheId({ rcAccountId }) {
18
+ return `${rcAccountId}-${MANAGED_OAUTH_ACCOUNT_DATA_KEY}`;
19
+ }
20
+
21
+ function isFilled(value) {
22
+ return value !== undefined && value !== null && value !== '';
23
+ }
24
+
25
+ function encryptStoredValue(value) {
26
+ return {
27
+ version: 1,
28
+ encrypted: true,
29
+ value: encode(JSON.stringify(value))
30
+ };
31
+ }
32
+
33
+ function decryptStoredValue(value) {
34
+ if (!value) {
35
+ return undefined;
36
+ }
37
+ if (value?.encrypted && value?.value) {
38
+ return JSON.parse(decoded(value.value));
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function normalizeManagedOAuthValues(values = {}) {
44
+ const normalized = {};
45
+ MANAGED_OAUTH_FIELDS.forEach(field => {
46
+ if (isFilled(values[field])) {
47
+ normalized[field] = values[field];
48
+ }
49
+ });
50
+ return normalized;
51
+ }
52
+
53
+ function buildStoredData(values = {}) {
54
+ const normalized = normalizeManagedOAuthValues(values);
55
+ const fields = {};
56
+ Object.keys(normalized).forEach(key => {
57
+ fields[key] = encryptStoredValue(normalized[key]);
58
+ });
59
+ return { fields };
60
+ }
61
+
62
+ function decryptStoredData(data = {}) {
63
+ const fields = data?.fields ?? {};
64
+ const values = {};
65
+ Object.keys(fields).forEach(key => {
66
+ values[key] = decryptStoredValue(fields[key]);
67
+ });
68
+ return values;
69
+ }
70
+
71
+ function removeSecret(values = {}) {
72
+ const sanitized = { ...values };
73
+ delete sanitized.clientSecret;
74
+ return sanitized;
75
+ }
76
+
77
+ async function getAccountManagedOAuthRecord({ rcAccountId, platform }) {
78
+ if (!rcAccountId || !platform) {
79
+ return null;
80
+ }
81
+ return AccountDataModel.findOne({
82
+ where: {
83
+ rcAccountId,
84
+ platformName: platform,
85
+ dataKey: MANAGED_OAUTH_ACCOUNT_DATA_KEY
86
+ }
87
+ });
88
+ }
89
+
90
+ async function getPendingManagedOAuthRecord({ rcAccountId }) {
91
+ if (!rcAccountId) {
92
+ return null;
93
+ }
94
+ const record = await CacheModel.findByPk(getPendingManagedOAuthCacheId({ rcAccountId }));
95
+ if (!record) {
96
+ return null;
97
+ }
98
+ if (record.expiry && new Date(record.expiry).getTime() <= Date.now()) {
99
+ await record.destroy();
100
+ return null;
101
+ }
102
+ return record;
103
+ }
104
+
105
+ async function getAccountManagedOAuthValues({ rcAccountId, platform }) {
106
+ const record = await getAccountManagedOAuthRecord({ rcAccountId, platform });
107
+ return decryptStoredData(record?.data);
108
+ }
109
+
110
+ async function getPendingManagedOAuthValues({ rcAccountId }) {
111
+ const record = await getPendingManagedOAuthRecord({ rcAccountId });
112
+ return decryptStoredData(record?.data);
113
+ }
114
+
115
+ async function getManagedOAuthState({ rcAccountId, platform, isAdmin }) {
116
+ const accountValues = await getAccountManagedOAuthValues({ rcAccountId, platform });
117
+ const hasAccountOAuth = Object.keys(accountValues).length > 0;
118
+ const pendingValues = isAdmin && !hasAccountOAuth
119
+ ? await getPendingManagedOAuthValues({ rcAccountId })
120
+ : {};
121
+ const hasPendingOAuth = Object.keys(pendingValues).length > 0;
122
+
123
+ return {
124
+ isAdmin: !!isAdmin,
125
+ hasAccountOAuth,
126
+ hasPendingOAuth,
127
+ ...(hasAccountOAuth ? { oauthValues: removeSecret(accountValues) } : {}),
128
+ ...(isAdmin && hasPendingOAuth ? { pendingValues } : {})
129
+ };
130
+ }
131
+
132
+ async function upsertPendingManagedOAuth({ rcAccountId, values = {} }) {
133
+ if (!rcAccountId) {
134
+ throw new Error('rcAccountId is required');
135
+ }
136
+ const id = getPendingManagedOAuthCacheId({ rcAccountId });
137
+ const data = buildStoredData(values);
138
+ const expiry = new Date(Date.now() + MANAGED_OAUTH_CACHE_TTL_MS);
139
+ const existing = await CacheModel.findByPk(id);
140
+ if (existing) {
141
+ await existing.update({
142
+ status: 'pending',
143
+ userId: rcAccountId,
144
+ cacheKey: MANAGED_OAUTH_ACCOUNT_DATA_KEY,
145
+ data,
146
+ expiry
147
+ });
148
+ return existing;
149
+ }
150
+ return CacheModel.create({
151
+ id,
152
+ status: 'pending',
153
+ userId: rcAccountId,
154
+ cacheKey: MANAGED_OAUTH_ACCOUNT_DATA_KEY,
155
+ data,
156
+ expiry
157
+ });
158
+ }
159
+
160
+ async function migratePendingManagedOAuth({ rcAccountId, platform }) {
161
+ const pending = await getPendingManagedOAuthRecord({ rcAccountId });
162
+ if (!pending) {
163
+ return false;
164
+ }
165
+ const existing = await getAccountManagedOAuthRecord({ rcAccountId, platform });
166
+ const data = pending.data ?? { fields: {} };
167
+ if (existing) {
168
+ await existing.update({ data });
169
+ }
170
+ else {
171
+ await AccountDataModel.create({
172
+ rcAccountId,
173
+ platformName: platform,
174
+ dataKey: MANAGED_OAUTH_ACCOUNT_DATA_KEY,
175
+ data
176
+ });
177
+ }
178
+ await pending.destroy();
179
+ return true;
180
+ }
181
+
182
+ async function resolveManagedOAuthInfo({ rcAccountId, platform }) {
183
+ const accountValues = await getAccountManagedOAuthValues({ rcAccountId, platform });
184
+ if (Object.keys(accountValues).length > 0) {
185
+ return {
186
+ source: 'account',
187
+ oauthInfo: accountValues
188
+ };
189
+ }
190
+ const pendingValues = await getPendingManagedOAuthValues({ rcAccountId });
191
+ if (Object.keys(pendingValues).length > 0) {
192
+ return {
193
+ source: 'pending',
194
+ oauthInfo: pendingValues
195
+ };
196
+ }
197
+ return {
198
+ source: null,
199
+ oauthInfo: null
200
+ };
201
+ }
202
+
203
+ async function clearPendingManagedOAuth({ rcAccountId }) {
204
+ if (!rcAccountId) {
205
+ return 0;
206
+ }
207
+ return CacheModel.destroy({
208
+ where: {
209
+ id: getPendingManagedOAuthCacheId({ rcAccountId })
210
+ }
211
+ });
212
+ }
213
+
214
+ async function clearAccountManagedOAuth({ rcAccountId, platform }) {
215
+ if (!rcAccountId || !platform) {
216
+ return 0;
217
+ }
218
+ return AccountDataModel.destroy({
219
+ where: {
220
+ rcAccountId,
221
+ platformName: platform,
222
+ dataKey: MANAGED_OAUTH_ACCOUNT_DATA_KEY
223
+ }
224
+ });
225
+ }
226
+
227
+ async function resetManagedOAuth({ rcAccountId, platform }) {
228
+ const deletedAccountCount = await clearAccountManagedOAuth({ rcAccountId, platform });
229
+ const deletedPendingCount = await clearPendingManagedOAuth({ rcAccountId });
230
+ return {
231
+ deletedAccountCount,
232
+ deletedPendingCount
233
+ };
234
+ }
235
+
236
+ exports.MANAGED_OAUTH_ACCOUNT_DATA_KEY = MANAGED_OAUTH_ACCOUNT_DATA_KEY;
237
+ exports.MANAGED_OAUTH_FIELDS = MANAGED_OAUTH_FIELDS;
238
+ exports.getManagedOAuthState = getManagedOAuthState;
239
+ exports.upsertPendingManagedOAuth = upsertPendingManagedOAuth;
240
+ exports.getPendingManagedOAuthValues = getPendingManagedOAuthValues;
241
+ exports.getAccountManagedOAuthValues = getAccountManagedOAuthValues;
242
+ exports.migratePendingManagedOAuth = migratePendingManagedOAuth;
243
+ exports.resolveManagedOAuthInfo = resolveManagedOAuthInfo;
244
+ exports.clearPendingManagedOAuth = clearPendingManagedOAuth;
245
+ exports.clearAccountManagedOAuth = clearAccountManagedOAuth;
246
+ exports.resetManagedOAuth = resetManagedOAuth;
247
+ exports.removeSecret = removeSecret;
@@ -1,209 +1,209 @@
1
- const { CacheModel } = require('../models/cacheModel');
2
- const { Op } = require('sequelize');
3
- const axios = require('axios');
4
- const { AccountDataModel } = require('../models/accountDataModel');
5
- const logger = require('../lib/logger');
6
-
7
- const PUBLIC_MANIFEST_BASE = 'https://appconnect.labs.ringcentral.com/public-api/connectors';
8
-
9
- async function getPluginsFromRcAccountId({ rcAccountId }) {
10
- const accountData = await AccountDataModel.findAll({
11
- where: {
12
- rcAccountId,
13
- dataKey: 'pluginData',
14
- },
15
- });
16
- const workingPlugins = accountData.map(data => ({
17
- id: data.platformName,
18
- data: data.data,
19
- }));
20
- return workingPlugins;
21
- }
22
-
23
- function getPluginConfigFromUserSettings({ userSettings, pluginId }) {
24
- if (!userSettings) {
25
- return null;
26
- }
27
- const targetPluginSettings = userSettings[`plugin_${pluginId}`];
28
- if (!targetPluginSettings?.value?.config) {
29
- return null;
30
- }
31
- return targetPluginSettings.value.config;
32
- }
33
-
34
- async function getPluginLicenseStatus({ rcAccountId, pluginId }) {
35
- const accountData = await AccountDataModel.findOne({
36
- where: {
37
- rcAccountId,
38
- platformName: pluginId,
39
- dataKey: 'pluginData',
40
- },
41
- });
42
- if (!accountData) {
43
- return null;
44
- }
45
- const licenseStatusUrl = accountData.data.licenseStatusUrl;
46
- const licenseStatusResponse = await axios.get(licenseStatusUrl, {
47
- headers: {
48
- 'Authorization': `Bearer ${accountData.data.jwtToken}`
49
- }
50
- });
51
- return licenseStatusResponse.data;
52
- }
53
-
54
- async function getPluginAsyncTasks({ asyncTaskIds }) {
55
- const caches = await CacheModel.findAll({
56
- where: {
57
- id: {
58
- [Op.in]: asyncTaskIds
59
- }
60
- }
61
- });
62
- const result = caches.map(cache => ({
63
- cacheKey: cache.cacheKey,
64
- status: cache.status
65
- }));
66
- const toRemoveCaches = caches.filter(cache => cache.status === 'completed' || cache.status === 'failed');
67
- await CacheModel.destroy({
68
- where: {
69
- id: {
70
- [Op.in]: toRemoveCaches.map(cache => cache.id)
71
- }
72
- }
73
- });
74
- return result;
75
- }
76
-
77
- function getRefreshedJwtTokenFromHeaders({ headers }) {
78
- if (!headers) {
79
- return null;
80
- }
81
- return headers['x-refreshed-jwt-token'] || headers['X-Refreshed-Jwt-Token'] || null;
82
- }
83
-
84
- async function resolvePluginManifest({ pluginId, pluginAccess, rcAccountId, pluginName }) {
85
- const manifestFetchers = [];
86
- if (pluginAccess === 'public') {
87
- manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
88
- } else if (pluginAccess === 'private' || pluginAccess === 'shared') {
89
- manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${rcAccountId}`);
90
- } else {
91
- manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
92
- manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${rcAccountId}`);
93
- }
94
-
95
- let pluginData = null;
96
- let lastError = null;
97
- for (const url of manifestFetchers) {
98
- try {
99
- const pluginDataResponse = await axios.get(url);
100
- pluginData = pluginDataResponse.data;
101
- break;
102
- } catch (error) {
103
- lastError = error;
104
- }
105
- }
106
-
107
- if (!pluginData) {
108
- throw lastError || new Error(`Unable to resolve manifest for plugin ${pluginId}`);
109
- }
110
-
111
- const platformKey = pluginName || Object.keys(pluginData.platforms || {})[0];
112
- if (!platformKey || !pluginData.platforms?.[platformKey]) {
113
- throw new Error(`Unable to resolve platform manifest for plugin ${pluginId}`);
114
- }
115
-
116
- return {
117
- pluginData,
118
- pluginManifest: pluginData.platforms[platformKey],
119
- platformKey,
120
- };
121
- }
122
-
123
- async function persistPluginData({ rcAccountId, pluginId, jwtToken, pluginData = {} }) {
124
- try {
125
- const accountData = await AccountDataModel.findOne({
126
- where: {
127
- rcAccountId,
128
- platformName: pluginId
129
- },
130
- });
131
- if (!accountData) {
132
- await AccountDataModel.create({
133
- rcAccountId,
134
- platformName: pluginId,
135
- dataKey: 'pluginData',
136
- data: {
137
- jwtToken,
138
- ...pluginData,
139
- },
140
- });
141
- } else {
142
- await accountData.update({
143
- data: {
144
- jwtToken,
145
- ...pluginData,
146
- },
147
- });
148
- }
149
- } catch (error) {
150
- logger.error('Failed to persist plugin data', {
151
- pluginId,
152
- rcAccountId,
153
- message: error.message,
154
- });
155
- }
156
- }
157
-
158
- async function registerPluginAccount({ pluginId, rcAccessToken, rcAccountId, pluginAccess, pluginName }) {
159
- const { pluginManifest } = await resolvePluginManifest({ pluginId, pluginAccess, rcAccountId, pluginName });
160
- if (!pluginManifest?.endpointUrl) {
161
- throw new Error(`Plugin endpoint URL not found for ${pluginId}`);
162
- }
163
-
164
- const registerUrl = pluginManifest.userRegisterEndpointUrl;
165
- const registerResponse = await axios.post(registerUrl, {
166
- rcAccessToken,
167
- rcAccountId: rcAccountId?.toString(),
168
- });
169
- const pluginJwtToken = registerResponse.data?.jwtToken;
170
- if (!pluginJwtToken) {
171
- throw new Error('Plugin register API did not return jwtToken');
172
- }
173
-
174
- await persistPluginData({
175
- rcAccountId: rcAccountId?.toString(),
176
- pluginId,
177
- jwtToken: pluginJwtToken,
178
- pluginData: pluginManifest,
179
- });
180
-
181
- return {
182
- successful: true,
183
- registerUrl,
184
- jwtToken: pluginJwtToken,
185
- };
186
- }
187
-
188
- async function unregisterPluginAccount({ pluginId, rcAccountId }) {
189
- const accountData = await AccountDataModel.findOne({
190
- where: {
191
- rcAccountId,
192
- platformName: pluginId,
193
- dataKey: 'pluginData'
194
- },
195
- });
196
- if (accountData) {
197
- await accountData.destroy();
198
- }
199
- }
200
-
201
- exports.getPluginsFromRcAccountId = getPluginsFromRcAccountId;
202
- exports.getPluginConfigFromUserSettings = getPluginConfigFromUserSettings;
203
- exports.getPluginLicenseStatus = getPluginLicenseStatus;
204
- exports.getPluginAsyncTasks = getPluginAsyncTasks;
205
- exports.getRefreshedJwtTokenFromHeaders = getRefreshedJwtTokenFromHeaders;
206
- exports.resolvePluginManifest = resolvePluginManifest;
207
- exports.persistPluginData = persistPluginData;
208
- exports.registerPluginAccount = registerPluginAccount;
1
+ const { CacheModel } = require('../models/cacheModel');
2
+ const { Op } = require('sequelize');
3
+ const axios = require('axios');
4
+ const { AccountDataModel } = require('../models/accountDataModel');
5
+ const logger = require('../lib/logger');
6
+
7
+ const PUBLIC_MANIFEST_BASE = 'https://appconnect.labs.ringcentral.com/public-api/connectors';
8
+
9
+ async function getPluginsFromRcAccountId({ rcAccountId }) {
10
+ const accountData = await AccountDataModel.findAll({
11
+ where: {
12
+ rcAccountId,
13
+ dataKey: 'pluginData',
14
+ },
15
+ });
16
+ const workingPlugins = accountData.map(data => ({
17
+ id: data.platformName,
18
+ data: data.data,
19
+ }));
20
+ return workingPlugins;
21
+ }
22
+
23
+ function getPluginConfigFromUserSettings({ userSettings, pluginId }) {
24
+ if (!userSettings) {
25
+ return null;
26
+ }
27
+ const targetPluginSettings = userSettings[`plugin_${pluginId}`];
28
+ if (!targetPluginSettings?.value?.config) {
29
+ return null;
30
+ }
31
+ return targetPluginSettings.value.config;
32
+ }
33
+
34
+ async function getPluginLicenseStatus({ rcAccountId, pluginId }) {
35
+ const accountData = await AccountDataModel.findOne({
36
+ where: {
37
+ rcAccountId,
38
+ platformName: pluginId,
39
+ dataKey: 'pluginData',
40
+ },
41
+ });
42
+ if (!accountData) {
43
+ return null;
44
+ }
45
+ const licenseStatusUrl = accountData.data.licenseStatusUrl;
46
+ const licenseStatusResponse = await axios.get(licenseStatusUrl, {
47
+ headers: {
48
+ 'Authorization': `Bearer ${accountData.data.jwtToken}`
49
+ }
50
+ });
51
+ return licenseStatusResponse.data;
52
+ }
53
+
54
+ async function getPluginAsyncTasks({ asyncTaskIds }) {
55
+ const caches = await CacheModel.findAll({
56
+ where: {
57
+ id: {
58
+ [Op.in]: asyncTaskIds
59
+ }
60
+ }
61
+ });
62
+ const result = caches.map(cache => ({
63
+ cacheKey: cache.cacheKey,
64
+ status: cache.status
65
+ }));
66
+ const toRemoveCaches = caches.filter(cache => cache.status === 'completed' || cache.status === 'failed');
67
+ await CacheModel.destroy({
68
+ where: {
69
+ id: {
70
+ [Op.in]: toRemoveCaches.map(cache => cache.id)
71
+ }
72
+ }
73
+ });
74
+ return result;
75
+ }
76
+
77
+ function getRefreshedJwtTokenFromHeaders({ headers }) {
78
+ if (!headers) {
79
+ return null;
80
+ }
81
+ return headers['x-refreshed-jwt-token'] || headers['X-Refreshed-Jwt-Token'] || null;
82
+ }
83
+
84
+ async function resolvePluginManifest({ pluginId, pluginAccess, rcAccountId, pluginName }) {
85
+ const manifestFetchers = [];
86
+ if (pluginAccess === 'public') {
87
+ manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
88
+ } else if (pluginAccess === 'private' || pluginAccess === 'shared') {
89
+ manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${rcAccountId}`);
90
+ } else {
91
+ manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?type=plugin`);
92
+ manifestFetchers.push(`${PUBLIC_MANIFEST_BASE}/${pluginId}/manifest?access=internal&type=plugin&accountId=${rcAccountId}`);
93
+ }
94
+
95
+ let pluginData = null;
96
+ let lastError = null;
97
+ for (const url of manifestFetchers) {
98
+ try {
99
+ const pluginDataResponse = await axios.get(url);
100
+ pluginData = pluginDataResponse.data;
101
+ break;
102
+ } catch (error) {
103
+ lastError = error;
104
+ }
105
+ }
106
+
107
+ if (!pluginData) {
108
+ throw lastError || new Error(`Unable to resolve manifest for plugin ${pluginId}`);
109
+ }
110
+
111
+ const platformKey = pluginName || Object.keys(pluginData.platforms || {})[0];
112
+ if (!platformKey || !pluginData.platforms?.[platformKey]) {
113
+ throw new Error(`Unable to resolve platform manifest for plugin ${pluginId}`);
114
+ }
115
+
116
+ return {
117
+ pluginData,
118
+ pluginManifest: pluginData.platforms[platformKey],
119
+ platformKey,
120
+ };
121
+ }
122
+
123
+ async function persistPluginData({ rcAccountId, pluginId, jwtToken, pluginData = {} }) {
124
+ try {
125
+ const accountData = await AccountDataModel.findOne({
126
+ where: {
127
+ rcAccountId,
128
+ platformName: pluginId
129
+ },
130
+ });
131
+ if (!accountData) {
132
+ await AccountDataModel.create({
133
+ rcAccountId,
134
+ platformName: pluginId,
135
+ dataKey: 'pluginData',
136
+ data: {
137
+ jwtToken,
138
+ ...pluginData,
139
+ },
140
+ });
141
+ } else {
142
+ await accountData.update({
143
+ data: {
144
+ jwtToken,
145
+ ...pluginData,
146
+ },
147
+ });
148
+ }
149
+ } catch (error) {
150
+ logger.error('Failed to persist plugin data', {
151
+ pluginId,
152
+ rcAccountId,
153
+ message: error.message,
154
+ });
155
+ }
156
+ }
157
+
158
+ async function registerPluginAccount({ pluginId, rcAccessToken, rcAccountId, pluginAccess, pluginName }) {
159
+ const { pluginManifest } = await resolvePluginManifest({ pluginId, pluginAccess, rcAccountId, pluginName });
160
+ if (!pluginManifest?.endpointUrl) {
161
+ throw new Error(`Plugin endpoint URL not found for ${pluginId}`);
162
+ }
163
+
164
+ const registerUrl = pluginManifest.userRegisterEndpointUrl;
165
+ const registerResponse = await axios.post(registerUrl, {
166
+ rcAccessToken,
167
+ rcAccountId: rcAccountId?.toString(),
168
+ });
169
+ const pluginJwtToken = registerResponse.data?.jwtToken;
170
+ if (!pluginJwtToken) {
171
+ throw new Error('Plugin register API did not return jwtToken');
172
+ }
173
+
174
+ await persistPluginData({
175
+ rcAccountId: rcAccountId?.toString(),
176
+ pluginId,
177
+ jwtToken: pluginJwtToken,
178
+ pluginData: pluginManifest,
179
+ });
180
+
181
+ return {
182
+ successful: true,
183
+ registerUrl,
184
+ jwtToken: pluginJwtToken,
185
+ };
186
+ }
187
+
188
+ async function unregisterPluginAccount({ pluginId, rcAccountId }) {
189
+ const accountData = await AccountDataModel.findOne({
190
+ where: {
191
+ rcAccountId,
192
+ platformName: pluginId,
193
+ dataKey: 'pluginData'
194
+ },
195
+ });
196
+ if (accountData) {
197
+ await accountData.destroy();
198
+ }
199
+ }
200
+
201
+ exports.getPluginsFromRcAccountId = getPluginsFromRcAccountId;
202
+ exports.getPluginConfigFromUserSettings = getPluginConfigFromUserSettings;
203
+ exports.getPluginLicenseStatus = getPluginLicenseStatus;
204
+ exports.getPluginAsyncTasks = getPluginAsyncTasks;
205
+ exports.getRefreshedJwtTokenFromHeaders = getRefreshedJwtTokenFromHeaders;
206
+ exports.resolvePluginManifest = resolvePluginManifest;
207
+ exports.persistPluginData = persistPluginData;
208
+ exports.registerPluginAccount = registerPluginAccount;
209
209
  exports.unregisterPluginAccount = unregisterPluginAccount;