@app-connect/core 1.7.22 → 1.7.24
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/docs/libraries.md +1 -1
- package/handlers/admin.js +7 -4
- package/handlers/auth.js +13 -5
- package/handlers/log.js +217 -109
- package/handlers/plugin.js +183 -1
- package/handlers/user.js +1 -1
- package/index.js +165 -7
- package/lib/authSession.js +44 -12
- package/lib/callLogComposer.js +36 -36
- package/lib/util.js +0 -18
- package/mcp/mcpHandler.js +31 -8
- package/mcp/tools/checkAuthStatus.js +16 -14
- package/mcp/tools/getPublicConnectors.js +11 -1
- package/mcp/tools/getSessionInfo.js +90 -0
- package/mcp/tools/index.js +3 -1
- package/mcp/tools/logout.js +10 -1
- package/models/llmSessionModel.js +3 -0
- package/package.json +1 -1
- package/releaseNotes.json +20 -0
- package/test/handlers/admin.test.js +1 -2
- package/test/handlers/log.test.js +60 -0
- package/test/handlers/plugin.test.js +93 -0
- package/test/lib/callLogComposer.test.js +21 -21
- package/test/lib/util.test.js +1 -332
- package/test/mcp/tools/checkAuthStatus.test.js +82 -0
- package/test/mcp/tools/getSessionInfo.test.js +127 -0
- package/test/mcp/tools/logout.test.js +58 -0
- package/test/routes/managedAuthRoutes.test.js +0 -3
package/handlers/plugin.js
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
const { CacheModel } = require('../models/cacheModel');
|
|
2
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
|
+
}
|
|
3
53
|
|
|
4
54
|
async function getPluginAsyncTasks({ asyncTaskIds }) {
|
|
5
55
|
const caches = await CacheModel.findAll({
|
|
@@ -24,4 +74,136 @@ async function getPluginAsyncTasks({ asyncTaskIds }) {
|
|
|
24
74
|
return result;
|
|
25
75
|
}
|
|
26
76
|
|
|
27
|
-
|
|
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
|
+
exports.unregisterPluginAccount = unregisterPluginAccount;
|
package/handlers/user.js
CHANGED
|
@@ -71,7 +71,7 @@ async function getUserSettings({ user, rcAccessToken, rcAccountId }) {
|
|
|
71
71
|
config[k] = configFromadminSettings[k];
|
|
72
72
|
}
|
|
73
73
|
else {
|
|
74
|
-
config[k].customizable = configFromadminSettings[k]
|
|
74
|
+
config[k].customizable = configFromadminSettings[k]?.customizable ?? true;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
result[key].value.config = config;
|
package/index.js
CHANGED
|
@@ -89,6 +89,19 @@ async function initDB() {
|
|
|
89
89
|
await UserModel.sync();
|
|
90
90
|
logger.info('hashedRcExtensionId column added to users table');
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
// if LlmSessionModel doesn't have expiry column, add it
|
|
94
|
+
const llmSessionTableName = LlmSessionModel.getTableName();
|
|
95
|
+
const llmSessionTableSchema = await queryInterface.describeTable(llmSessionTableName);
|
|
96
|
+
if (!llmSessionTableSchema.expiry) {
|
|
97
|
+
logger.info('adding expiry column to llmSessions table...');
|
|
98
|
+
await queryInterface.addColumn(llmSessionTableName, 'expiry', {
|
|
99
|
+
type: Sequelize.DATE,
|
|
100
|
+
allowNull: true,
|
|
101
|
+
});
|
|
102
|
+
await LlmSessionModel.sync();
|
|
103
|
+
logger.info('expiry column added to llmSessions table');
|
|
104
|
+
}
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
|
|
@@ -1165,13 +1178,13 @@ function createCoreRouter() {
|
|
|
1165
1178
|
res.status(400).send(tracer ? tracer.wrapResponse('Missing platform name') : 'Missing platform name');
|
|
1166
1179
|
return;
|
|
1167
1180
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const rcUserTokenResult = await adminCore.validateRcUserToken({ rcAccessToken });
|
|
1172
|
-
rcAccountId = rcUserTokenResult.rcAccountId;
|
|
1173
|
-
rcExtensionId = rcUserTokenResult.rcExtensionId;
|
|
1181
|
+
if (!rcAccessToken) {
|
|
1182
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Missing RingCentral access token') : 'Missing RingCentral access token');
|
|
1183
|
+
return;
|
|
1174
1184
|
}
|
|
1185
|
+
const rcUserTokenResult = await adminCore.validateRcUserToken({ rcAccessToken });
|
|
1186
|
+
const rcAccountId = rcUserTokenResult.rcAccountId;
|
|
1187
|
+
const rcExtensionId = rcUserTokenResult.rcExtensionId;
|
|
1175
1188
|
const { userInfo, returnMessage } = await authCore.onApiKeyLogin({
|
|
1176
1189
|
platform,
|
|
1177
1190
|
hostname,
|
|
@@ -2197,7 +2210,7 @@ function createCoreRouter() {
|
|
|
2197
2210
|
let platformName = null;
|
|
2198
2211
|
let success = false;
|
|
2199
2212
|
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
2200
|
-
const
|
|
2213
|
+
const jwtToken = req.jwtToken || req.query.jwtToken;
|
|
2201
2214
|
try {
|
|
2202
2215
|
if (!jwtToken) {
|
|
2203
2216
|
tracer?.trace('pluginAsyncTask:noToken', {});
|
|
@@ -2239,6 +2252,151 @@ function createCoreRouter() {
|
|
|
2239
2252
|
});
|
|
2240
2253
|
});
|
|
2241
2254
|
|
|
2255
|
+
router.post('/plugin/register', async function (req, res) {
|
|
2256
|
+
const requestStartTime = new Date().getTime();
|
|
2257
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
2258
|
+
tracer?.trace('pluginRegister:start', { body: req.body });
|
|
2259
|
+
let success = false;
|
|
2260
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
2261
|
+
try {
|
|
2262
|
+
const { pluginId, rcAccountId, pluginAccess, pluginName } = req.body || {};
|
|
2263
|
+
const rcAccessToken = req.query?.rcAccessToken;
|
|
2264
|
+
if (!pluginId || !rcAccountId) {
|
|
2265
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'pluginId and rcAccountId are required' }) : { successful: false, returnMessage: 'pluginId and rcAccountId are required' });
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
if (!rcAccessToken) {
|
|
2269
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'Missing RingCentral access token' }) : { successful: false, returnMessage: 'Missing RingCentral access token' });
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
const { isValidated, rcAccountId: validatedRcAccountId } = await adminCore.validateAdminRole({ rcAccessToken });
|
|
2273
|
+
if (!isValidated) {
|
|
2274
|
+
res.status(403).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'Admin validation failed' }) : { successful: false, returnMessage: 'Admin validation failed' });
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
if (validatedRcAccountId?.toString() !== rcAccountId?.toString()) {
|
|
2278
|
+
res.status(403).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'rcAccountId mismatch' }) : { successful: false, returnMessage: 'rcAccountId mismatch' });
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
await pluginCore.registerPluginAccount({
|
|
2283
|
+
pluginId,
|
|
2284
|
+
rcAccessToken,
|
|
2285
|
+
rcAccountId: rcAccountId?.toString(),
|
|
2286
|
+
pluginAccess,
|
|
2287
|
+
pluginName,
|
|
2288
|
+
});
|
|
2289
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
2290
|
+
success = true;
|
|
2291
|
+
} catch (e) {
|
|
2292
|
+
logger.error('Plugin register failed', { stack: e.stack });
|
|
2293
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: e.message || e }) : { successful: false, returnMessage: e.message || e });
|
|
2294
|
+
tracer?.traceError('pluginRegister:error', e);
|
|
2295
|
+
success = false;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
const requestEndTime = new Date().getTime();
|
|
2299
|
+
analytics.track({
|
|
2300
|
+
eventName: 'Plugin Register',
|
|
2301
|
+
interfaceName: 'pluginRegister',
|
|
2302
|
+
accountId: hashedAccountId,
|
|
2303
|
+
extensionId: hashedExtensionId,
|
|
2304
|
+
success,
|
|
2305
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
2306
|
+
userAgent,
|
|
2307
|
+
ip,
|
|
2308
|
+
author,
|
|
2309
|
+
eventAddedVia
|
|
2310
|
+
});
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
router.delete('/plugin/unregister', async function (req, res) {
|
|
2314
|
+
const requestStartTime = new Date().getTime();
|
|
2315
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
2316
|
+
tracer?.trace('pluginUnregister:start', { query: req.query });
|
|
2317
|
+
let success = false;
|
|
2318
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
2319
|
+
try {
|
|
2320
|
+
const { pluginId, rcAccountId, pluginName } = req.query || {};
|
|
2321
|
+
const rcAccessToken = req.query?.rcAccessToken;
|
|
2322
|
+
if (!pluginId || !rcAccountId) {
|
|
2323
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'pluginId and rcAccountId are required' }) : { successful: false, returnMessage: 'pluginId and rcAccountId are required' });
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
if (!rcAccessToken) {
|
|
2327
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'Missing RingCentral access token' }) : { successful: false, returnMessage: 'Missing RingCentral access token' });
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
const { isValidated, rcAccountId: validatedRcAccountId } = await adminCore.validateAdminRole({ rcAccessToken });
|
|
2331
|
+
if (!isValidated) {
|
|
2332
|
+
res.status(403).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'Admin validation failed' }) : { successful: false, returnMessage: 'Admin validation failed' });
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
if (validatedRcAccountId?.toString() !== rcAccountId?.toString()) {
|
|
2336
|
+
res.status(403).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: 'rcAccountId mismatch' }) : { successful: false, returnMessage: 'rcAccountId mismatch' });
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
await pluginCore.unregisterPluginAccount({
|
|
2341
|
+
pluginId,
|
|
2342
|
+
rcAccountId: rcAccountId?.toString(),
|
|
2343
|
+
pluginName,
|
|
2344
|
+
});
|
|
2345
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
|
|
2346
|
+
success = true;
|
|
2347
|
+
} catch (e) {
|
|
2348
|
+
logger.error('Plugin unregister failed', { stack: e.stack });
|
|
2349
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: e.message || e }) : { successful: false, returnMessage: e.message || e });
|
|
2350
|
+
tracer?.traceError('pluginUnregister:error', e);
|
|
2351
|
+
success = false;
|
|
2352
|
+
}
|
|
2353
|
+
const requestEndTime = new Date().getTime();
|
|
2354
|
+
analytics.track({
|
|
2355
|
+
eventName: 'Plugin Unregister',
|
|
2356
|
+
interfaceName: 'pluginUnregister',
|
|
2357
|
+
accountId: hashedAccountId,
|
|
2358
|
+
extensionId: hashedExtensionId,
|
|
2359
|
+
success,
|
|
2360
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
2361
|
+
userAgent,
|
|
2362
|
+
ip,
|
|
2363
|
+
author,
|
|
2364
|
+
eventAddedVia
|
|
2365
|
+
});
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
router.get('/plugin/licenseStatus', async function (req, res) {
|
|
2369
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
2370
|
+
tracer?.trace('getPluginLicenseStatus:start', { query: req.query });
|
|
2371
|
+
try {
|
|
2372
|
+
const jwtToken = req.jwtToken || req.query.jwtToken;
|
|
2373
|
+
if (!jwtToken) {
|
|
2374
|
+
tracer?.trace('getPluginLicenseStatus:noToken', {});
|
|
2375
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
2379
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
2380
|
+
if (!user) {
|
|
2381
|
+
tracer?.trace('getPluginLicenseStatus:userNotFound', {});
|
|
2382
|
+
res.status(400).send(tracer ? tracer.wrapResponse('User not found') : 'User not found');
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
const { rcAccountId, pluginId } = req.query;
|
|
2386
|
+
if (!rcAccountId || !pluginId) {
|
|
2387
|
+
res.status(400).send(tracer ? tracer.wrapResponse('rcAccountId and pluginId are required') : 'rcAccountId and pluginId are required');
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const licenseStatus = await pluginCore.getPluginLicenseStatus({ rcAccountId, pluginId });
|
|
2391
|
+
res.status(200).send(tracer ? tracer.wrapResponse(licenseStatus) : licenseStatus);
|
|
2392
|
+
}
|
|
2393
|
+
catch (e) {
|
|
2394
|
+
logger.error('Get plugin license status failed', { stack: e.stack });
|
|
2395
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ licenseStatus: false, licenseStatusDescription: e.message || e }) : { licenseStatus: false, licenseStatusDescription: e.message || e });
|
|
2396
|
+
tracer?.traceError('getPluginLicenseStatus:error', e);
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
|
|
2242
2400
|
if (process.env.IS_PROD === 'false') {
|
|
2243
2401
|
router.post('/registerMockUser', async function (req, res) {
|
|
2244
2402
|
const secretKey = req.query.secretKey;
|
package/lib/authSession.js
CHANGED
|
@@ -7,7 +7,20 @@
|
|
|
7
7
|
const { CacheModel } = require('../models/cacheModel');
|
|
8
8
|
|
|
9
9
|
const AUTH_SESSION_PREFIX = 'auth-session';
|
|
10
|
-
const
|
|
10
|
+
const PENDING_SESSION_EXPIRY_MINUTES = 5;
|
|
11
|
+
const SETTLED_SESSION_EXPIRY_MINUTES = 15;
|
|
12
|
+
|
|
13
|
+
function getSessionRecordId(sessionId) {
|
|
14
|
+
return `${AUTH_SESSION_PREFIX}-${sessionId}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getExpiry(minutes) {
|
|
18
|
+
return new Date(Date.now() + minutes * 60 * 1000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isExpired(record) {
|
|
22
|
+
return Boolean(record?.expiry && record.expiry <= new Date());
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
/**
|
|
13
26
|
* Create (or reset) an auth session.
|
|
@@ -16,9 +29,13 @@ const SESSION_EXPIRY_MINUTES = 5;
|
|
|
16
29
|
* correctly for the new attempt.
|
|
17
30
|
*/
|
|
18
31
|
async function createAuthSession(sessionId, data) {
|
|
19
|
-
const id =
|
|
20
|
-
const expiry =
|
|
21
|
-
const sessionData = {
|
|
32
|
+
const id = getSessionRecordId(sessionId);
|
|
33
|
+
const expiry = getExpiry(PENDING_SESSION_EXPIRY_MINUTES);
|
|
34
|
+
const sessionData = {
|
|
35
|
+
platform: data.platform,
|
|
36
|
+
hostname: data.hostname || '',
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
};
|
|
22
39
|
|
|
23
40
|
const existing = await CacheModel.findByPk(id);
|
|
24
41
|
if (existing) {
|
|
@@ -39,10 +56,21 @@ async function createAuthSession(sessionId, data) {
|
|
|
39
56
|
* Get an auth session by ID
|
|
40
57
|
*/
|
|
41
58
|
async function getAuthSession(sessionId) {
|
|
42
|
-
const record = await CacheModel.findByPk(
|
|
43
|
-
|
|
59
|
+
const record = await CacheModel.findByPk(getSessionRecordId(sessionId));
|
|
60
|
+
|
|
44
61
|
if (!record) return null;
|
|
45
|
-
|
|
62
|
+
|
|
63
|
+
if (isExpired(record)) {
|
|
64
|
+
if (record.status !== 'expired') {
|
|
65
|
+
await record.update({ status: 'expired' });
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
sessionId: record.userId,
|
|
69
|
+
status: 'expired',
|
|
70
|
+
...record.data
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
46
74
|
return {
|
|
47
75
|
sessionId: record.userId,
|
|
48
76
|
status: record.status,
|
|
@@ -54,18 +82,22 @@ async function getAuthSession(sessionId) {
|
|
|
54
82
|
* Update an auth session
|
|
55
83
|
*/
|
|
56
84
|
async function updateAuthSession(sessionId, data) {
|
|
57
|
-
const record = await CacheModel.findByPk(
|
|
58
|
-
|
|
85
|
+
const record = await CacheModel.findByPk(getSessionRecordId(sessionId));
|
|
86
|
+
|
|
59
87
|
if (!record) return;
|
|
60
|
-
|
|
88
|
+
|
|
61
89
|
const existingData = record.data || {};
|
|
90
|
+
const nextStatus = data.status || record.status;
|
|
62
91
|
await record.update({
|
|
63
|
-
status:
|
|
92
|
+
status: nextStatus,
|
|
64
93
|
data: {
|
|
65
94
|
...existingData,
|
|
66
95
|
...data,
|
|
67
96
|
updatedAt: new Date().toISOString()
|
|
68
|
-
}
|
|
97
|
+
},
|
|
98
|
+
expiry: nextStatus === 'pending'
|
|
99
|
+
? getExpiry(PENDING_SESSION_EXPIRY_MINUTES)
|
|
100
|
+
: getExpiry(SETTLED_SESSION_EXPIRY_MINUTES)
|
|
69
101
|
});
|
|
70
102
|
}
|
|
71
103
|
|
package/lib/callLogComposer.js
CHANGED
|
@@ -702,27 +702,27 @@ function upsertRingSenseTranscript({ body, transcript, logFormat }) {
|
|
|
702
702
|
switch (logFormat) {
|
|
703
703
|
case LOG_DETAILS_FORMAT_TYPE.HTML:
|
|
704
704
|
const formattedTranscript = clearedTranscript.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
705
|
-
transcriptRegex = /<div><b>
|
|
705
|
+
transcriptRegex = /<div><b>ACE transcript<\/b><br>(.+?)<\/div>/;
|
|
706
706
|
if (transcriptRegex.test(result)) {
|
|
707
|
-
result = result.replace(transcriptRegex, `<div><b>
|
|
707
|
+
result = result.replace(transcriptRegex, `<div><b>ACE transcript</b><br>${formattedTranscript}</div>`);
|
|
708
708
|
} else {
|
|
709
|
-
result += `<div><b>
|
|
709
|
+
result += `<div><b>ACE transcript</b><br>${formattedTranscript}</div>`;
|
|
710
710
|
}
|
|
711
711
|
break;
|
|
712
712
|
case LOG_DETAILS_FORMAT_TYPE.MARKDOWN:
|
|
713
|
-
transcriptRegex = /###
|
|
713
|
+
transcriptRegex = /### ACE transcript\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
714
714
|
if (transcriptRegex.test(result)) {
|
|
715
|
-
result = result.replace(transcriptRegex, `###
|
|
715
|
+
result = result.replace(transcriptRegex, `### ACE transcript\n${clearedTranscript}\n`);
|
|
716
716
|
} else {
|
|
717
|
-
result += `###
|
|
717
|
+
result += `### ACE transcript\n${clearedTranscript}\n`;
|
|
718
718
|
}
|
|
719
719
|
break;
|
|
720
720
|
case LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT:
|
|
721
|
-
transcriptRegex = /-
|
|
721
|
+
transcriptRegex = /- ACE transcript:([\s\S]*?)--- END/;
|
|
722
722
|
if (transcriptRegex.test(result)) {
|
|
723
|
-
result = result.replace(transcriptRegex, `-
|
|
723
|
+
result = result.replace(transcriptRegex, `- ACE transcript:\n${clearedTranscript}\n--- END`);
|
|
724
724
|
} else {
|
|
725
|
-
result += `\n-
|
|
725
|
+
result += `\n- ACE transcript:\n${clearedTranscript}\n--- END\n`;
|
|
726
726
|
}
|
|
727
727
|
break;
|
|
728
728
|
}
|
|
@@ -739,28 +739,28 @@ function upsertRingSenseSummary({ body, summary, logFormat }) {
|
|
|
739
739
|
|
|
740
740
|
switch (logFormat) {
|
|
741
741
|
case LOG_DETAILS_FORMAT_TYPE.HTML:
|
|
742
|
-
summaryRegex = /<div><b>
|
|
742
|
+
summaryRegex = /<div><b>ACE summary<\/b><br>(.+?)<\/div>/;
|
|
743
743
|
const formattedSummary = clearedSummary.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
744
744
|
if (summaryRegex.test(result)) {
|
|
745
|
-
result = result.replace(summaryRegex, `<div><b>
|
|
745
|
+
result = result.replace(summaryRegex, `<div><b>ACE summary</b><br>${formattedSummary}</div>`);
|
|
746
746
|
} else {
|
|
747
|
-
result += `<div><b>
|
|
747
|
+
result += `<div><b>ACE summary</b><br>${formattedSummary}</div>`;
|
|
748
748
|
}
|
|
749
749
|
break;
|
|
750
750
|
case LOG_DETAILS_FORMAT_TYPE.MARKDOWN:
|
|
751
|
-
summaryRegex = /###
|
|
751
|
+
summaryRegex = /### ACE summary\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
752
752
|
if (summaryRegex.test(result)) {
|
|
753
|
-
result = result.replace(summaryRegex, `###
|
|
753
|
+
result = result.replace(summaryRegex, `### ACE summary\n${summary}\n`);
|
|
754
754
|
} else {
|
|
755
|
-
result += `###
|
|
755
|
+
result += `### ACE summary\n${summary}\n`;
|
|
756
756
|
}
|
|
757
757
|
break;
|
|
758
758
|
case LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT:
|
|
759
|
-
summaryRegex = /-
|
|
759
|
+
summaryRegex = /- ACE summary:([\s\S]*?)--- END/;
|
|
760
760
|
if (summaryRegex.test(result)) {
|
|
761
|
-
result = result.replace(summaryRegex, `-
|
|
761
|
+
result = result.replace(summaryRegex, `- ACE summary:\n${summary}\n--- END`);
|
|
762
762
|
} else {
|
|
763
|
-
result += `\n-
|
|
763
|
+
result += `\n- ACE summary:\n${summary}\n--- END\n`;
|
|
764
764
|
}
|
|
765
765
|
break;
|
|
766
766
|
}
|
|
@@ -811,28 +811,28 @@ function upsertRingSenseBulletedSummary({ body, summary, logFormat }) {
|
|
|
811
811
|
|
|
812
812
|
switch (logFormat) {
|
|
813
813
|
case LOG_DETAILS_FORMAT_TYPE.HTML:
|
|
814
|
-
summaryRegex = /<div><b>
|
|
814
|
+
summaryRegex = /<div><b>ACE bulleted summary<\/b><br>(.+?)<\/div>/;
|
|
815
815
|
const formattedSummary = clearedSummary.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
|
816
816
|
if (summaryRegex.test(result)) {
|
|
817
|
-
result = result.replace(summaryRegex, `<div><b>
|
|
817
|
+
result = result.replace(summaryRegex, `<div><b>ACE bulleted summary</b><br>${formattedSummary}</div>`);
|
|
818
818
|
} else {
|
|
819
|
-
result += `<div><b>
|
|
819
|
+
result += `<div><b>ACE bulleted summary</b><br>${formattedSummary}</div>`;
|
|
820
820
|
}
|
|
821
821
|
break;
|
|
822
822
|
case LOG_DETAILS_FORMAT_TYPE.MARKDOWN:
|
|
823
|
-
summaryRegex = /###
|
|
823
|
+
summaryRegex = /### ACE bulleted summary\n([\s\S]*?)(?=\n### |\n$|$)/;
|
|
824
824
|
if (summaryRegex.test(result)) {
|
|
825
|
-
result = result.replace(summaryRegex, `###
|
|
825
|
+
result = result.replace(summaryRegex, `### ACE bulleted summary\n${summary}\n`);
|
|
826
826
|
} else {
|
|
827
|
-
result += `###
|
|
827
|
+
result += `### ACE bulleted summary\n${summary}\n`;
|
|
828
828
|
}
|
|
829
829
|
break;
|
|
830
830
|
case LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT:
|
|
831
|
-
summaryRegex = /-
|
|
831
|
+
summaryRegex = /- ACE bulleted summary:\s*([^<\n]+)(?=\n|$)/i;
|
|
832
832
|
if (summaryRegex.test(result)) {
|
|
833
|
-
result = result.replace(summaryRegex, `-
|
|
833
|
+
result = result.replace(summaryRegex, `- ACE bulleted summary:\n${summary}\n--- END`);
|
|
834
834
|
} else {
|
|
835
|
-
result += `\n-
|
|
835
|
+
result += `\n- ACE bulleted summary:\n${summary}\n--- END\n`;
|
|
836
836
|
}
|
|
837
837
|
break;
|
|
838
838
|
}
|
|
@@ -847,27 +847,27 @@ function upsertRingSenseLink({ body, link, logFormat }) {
|
|
|
847
847
|
|
|
848
848
|
switch (logFormat) {
|
|
849
849
|
case LOG_DETAILS_FORMAT_TYPE.HTML:
|
|
850
|
-
linkRegex = /(?:<li>)?<b>
|
|
850
|
+
linkRegex = /(?:<li>)?<b>ACE recording link<\/b>:\s*(?:<a[^>]*>[^<]*<\/a>|[^<]+)(?:<\/li>|(?=<|$))/i;
|
|
851
851
|
if (linkRegex.test(result)) {
|
|
852
|
-
result = result.replace(linkRegex, `<li><b>
|
|
852
|
+
result = result.replace(linkRegex, `<li><b>ACE recording link</b>: <a target="_blank" href="${link}">open</a></li>`);
|
|
853
853
|
} else {
|
|
854
|
-
result += `<li><b>
|
|
854
|
+
result += `<li><b>ACE recording link</b>: <a target="_blank" href="${link}">open</a></li>`;
|
|
855
855
|
}
|
|
856
856
|
break;
|
|
857
857
|
case LOG_DETAILS_FORMAT_TYPE.MARKDOWN:
|
|
858
|
-
linkRegex = /\*\*
|
|
858
|
+
linkRegex = /\*\*ACE recording link\*\*:\s*([^<\n]+)(?=\n|$)/i;
|
|
859
859
|
if (linkRegex.test(result)) {
|
|
860
|
-
result = result.replace(linkRegex, `**
|
|
860
|
+
result = result.replace(linkRegex, `**ACE recording link**: ${link}\n`);
|
|
861
861
|
} else {
|
|
862
|
-
result += `**
|
|
862
|
+
result += `**ACE recording link**: ${link}\n`;
|
|
863
863
|
}
|
|
864
864
|
break;
|
|
865
865
|
case LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT:
|
|
866
|
-
linkRegex = /-
|
|
866
|
+
linkRegex = /- ACE recording link:\s*([^<\n]+)(?=\n|$)/i;
|
|
867
867
|
if (linkRegex.test(result)) {
|
|
868
|
-
result = result.replace(linkRegex, `-
|
|
868
|
+
result = result.replace(linkRegex, `- ACE recording link: ${link}`);
|
|
869
869
|
} else {
|
|
870
|
-
result += `-
|
|
870
|
+
result += `- ACE recording link: ${link}\n`;
|
|
871
871
|
}
|
|
872
872
|
break;
|
|
873
873
|
}
|
package/lib/util.js
CHANGED
|
@@ -60,26 +60,8 @@ function getMediaReaderLinkByPlatformMediaLink(platformMediaLink) {
|
|
|
60
60
|
return `https://ringcentral.github.io/ringcentral-media-reader/?media=${encodedPlatformMediaLink}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function getPluginsFromUserSettings({ userSettings, logType }) {
|
|
64
|
-
const result = [];
|
|
65
|
-
if (!userSettings) {
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
for (const userSettingKey in userSettings) {
|
|
69
|
-
if (!userSettingKey.startsWith('plugin_')) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const pluginUserSetting = userSettings[userSettingKey];
|
|
73
|
-
if (pluginUserSetting.value.logTypes.includes(logType)) {
|
|
74
|
-
result.push({ id: userSettingKey.replace('plugin_', ''), value: pluginUserSetting.value });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
exports.getTimeZone = getTimeZone;
|
|
81
64
|
exports.getHashValue = getHashValue;
|
|
82
65
|
exports.secondsToHoursMinutesSeconds = secondsToHoursMinutesSeconds;
|
|
83
66
|
exports.getMostRecentDate = getMostRecentDate;
|
|
84
67
|
exports.getMediaReaderLinkByPlatformMediaLink = getMediaReaderLinkByPlatformMediaLink;
|
|
85
|
-
exports.getPluginsFromUserSettings = getPluginsFromUserSettings;
|