@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.
- package/.env.test +5 -5
- package/README.md +441 -441
- package/connector/developerPortal.js +31 -31
- package/connector/mock.js +84 -77
- package/connector/proxy/engine.js +164 -164
- package/connector/proxy/index.js +500 -500
- package/connector/registry.js +252 -252
- package/docs/README.md +50 -50
- package/docs/architecture.md +93 -93
- package/docs/connectors.md +116 -116
- package/docs/handlers.md +125 -125
- package/docs/libraries.md +101 -101
- package/docs/models.md +144 -144
- package/docs/routes.md +115 -115
- package/docs/tests.md +73 -73
- package/handlers/admin.js +523 -523
- package/handlers/appointment.js +193 -0
- package/handlers/auth.js +320 -296
- package/handlers/calldown.js +99 -99
- package/handlers/contact.js +280 -280
- package/handlers/disposition.js +82 -80
- package/handlers/log.js +990 -973
- package/handlers/managedAuth.js +446 -446
- package/handlers/managedOAuth.js +247 -0
- package/handlers/plugin.js +208 -208
- package/handlers/user.js +142 -142
- package/index.js +3162 -2652
- package/jest.config.js +56 -56
- package/lib/analytics.js +54 -54
- package/lib/authSession.js +109 -109
- package/lib/cacheCleanup.js +21 -0
- package/lib/callLogComposer.js +898 -898
- package/lib/callLogLookup.js +34 -0
- package/lib/constants.js +8 -8
- package/lib/debugTracer.js +177 -177
- package/lib/encode.js +30 -30
- package/lib/errorHandler.js +218 -206
- package/lib/generalErrorMessage.js +41 -41
- package/lib/jwt.js +18 -18
- package/lib/logger.js +190 -190
- package/lib/migrateCallLogsSchema.js +116 -0
- package/lib/ringcentral.js +266 -266
- package/lib/s3ErrorLogReport.js +65 -65
- package/lib/sharedSMSComposer.js +471 -471
- package/lib/util.js +67 -67
- package/mcp/README.md +412 -395
- package/mcp/lib/validator.js +91 -91
- package/mcp/mcpHandler.js +425 -425
- package/mcp/tools/cancelAppointment.js +101 -0
- package/mcp/tools/checkAuthStatus.js +105 -105
- package/mcp/tools/confirmAppointment.js +101 -0
- package/mcp/tools/createAppointment.js +157 -0
- package/mcp/tools/createCallLog.js +327 -316
- package/mcp/tools/createContact.js +117 -117
- package/mcp/tools/createMessageLog.js +287 -287
- package/mcp/tools/doAuth.js +60 -60
- package/mcp/tools/findContactByName.js +93 -93
- package/mcp/tools/findContactByPhone.js +101 -101
- package/mcp/tools/getCallLog.js +111 -102
- package/mcp/tools/getGoogleFilePicker.js +99 -99
- package/mcp/tools/getHelp.js +43 -43
- package/mcp/tools/getPublicConnectors.js +94 -94
- package/mcp/tools/getSessionInfo.js +90 -90
- package/mcp/tools/index.js +51 -41
- package/mcp/tools/listAppointments.js +163 -0
- package/mcp/tools/logout.js +99 -96
- package/mcp/tools/rcGetCallLogs.js +65 -65
- package/mcp/tools/updateAppointment.js +154 -0
- package/mcp/tools/updateCallLog.js +130 -126
- package/mcp/ui/App/App.tsx +358 -358
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
- package/mcp/ui/App/components/ConnectorList.tsx +82 -82
- package/mcp/ui/App/components/DebugPanel.tsx +43 -43
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
- package/mcp/ui/App/lib/callTool.ts +130 -130
- package/mcp/ui/App/lib/debugLog.ts +41 -41
- package/mcp/ui/App/lib/developerPortal.ts +111 -111
- package/mcp/ui/App/main.css +5 -5
- package/mcp/ui/App/root.tsx +13 -13
- package/mcp/ui/index.html +13 -13
- package/mcp/ui/package-lock.json +6356 -6356
- package/mcp/ui/package.json +25 -25
- package/mcp/ui/tsconfig.json +26 -26
- package/mcp/ui/vite.config.ts +16 -16
- package/models/accountDataModel.js +33 -33
- package/models/adminConfigModel.js +35 -35
- package/models/cacheModel.js +30 -26
- package/models/callDownListModel.js +34 -34
- package/models/callLogModel.js +33 -27
- package/models/dynamo/connectorSchema.js +146 -146
- package/models/dynamo/lockSchema.js +24 -24
- package/models/dynamo/noteCacheSchema.js +29 -29
- package/models/llmSessionModel.js +17 -17
- package/models/messageLogModel.js +25 -25
- package/models/sequelize.js +16 -16
- package/models/userModel.js +45 -45
- package/package.json +1 -1
- package/releaseNotes.json +1101 -1081
- package/test/connector/proxy/engine.test.js +126 -126
- package/test/connector/proxy/index.test.js +279 -279
- package/test/connector/proxy/sample.json +161 -161
- package/test/connector/registry.test.js +415 -415
- package/test/handlers/admin.test.js +616 -616
- package/test/handlers/auth.test.js +1092 -1018
- package/test/handlers/contact.test.js +1014 -1014
- package/test/handlers/log.test.js +1298 -1160
- package/test/handlers/managedAuth.test.js +457 -457
- package/test/handlers/plugin.test.js +380 -380
- package/test/index.test.js +105 -105
- package/test/lib/cacheCleanup.test.js +42 -0
- package/test/lib/callLogComposer.test.js +1231 -1231
- package/test/lib/debugTracer.test.js +328 -328
- package/test/lib/jwt.test.js +176 -176
- package/test/lib/logger.test.js +206 -206
- package/test/lib/oauth.test.js +359 -359
- package/test/lib/ringcentral.test.js +467 -467
- package/test/lib/sharedSMSComposer.test.js +1084 -1084
- package/test/lib/util.test.js +329 -329
- package/test/mcp/tools/checkAuthStatus.test.js +83 -83
- package/test/mcp/tools/createCallLog.test.js +436 -436
- package/test/mcp/tools/createContact.test.js +58 -58
- package/test/mcp/tools/createMessageLog.test.js +595 -595
- package/test/mcp/tools/doAuth.test.js +113 -113
- package/test/mcp/tools/findContactByName.test.js +275 -275
- package/test/mcp/tools/findContactByPhone.test.js +296 -296
- package/test/mcp/tools/getCallLog.test.js +298 -298
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
- package/test/mcp/tools/getPublicConnectors.test.js +107 -107
- package/test/mcp/tools/getSessionInfo.test.js +127 -127
- package/test/mcp/tools/logout.test.js +233 -233
- package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
- package/test/mcp/tools/updateCallLog.test.js +360 -360
- package/test/models/accountDataModel.test.js +98 -98
- package/test/models/dynamo/connectorSchema.test.js +189 -189
- package/test/models/models.test.js +568 -539
- package/test/routes/managedAuthRoutes.test.js +235 -104
- 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;
|
package/handlers/plugin.js
CHANGED
|
@@ -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;
|