@app-connect/core 1.7.18 → 1.7.20
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/connector/proxy/index.js +2 -1
- package/handlers/auth.js +30 -55
- package/handlers/log.js +182 -10
- package/handlers/plugin.js +27 -0
- package/handlers/user.js +31 -2
- package/index.js +115 -22
- package/lib/authSession.js +21 -12
- package/lib/callLogComposer.js +1 -1
- package/lib/debugTracer.js +20 -2
- package/lib/util.js +21 -4
- package/mcp/README.md +395 -0
- package/mcp/mcpHandler.js +318 -82
- package/mcp/tools/checkAuthStatus.js +28 -35
- package/mcp/tools/createCallLog.js +13 -9
- package/mcp/tools/createContact.js +2 -6
- package/mcp/tools/doAuth.js +27 -157
- package/mcp/tools/findContactByName.js +6 -9
- package/mcp/tools/findContactByPhone.js +2 -6
- package/mcp/tools/getGoogleFilePicker.js +5 -9
- package/mcp/tools/getHelp.js +2 -3
- package/mcp/tools/getPublicConnectors.js +55 -24
- package/mcp/tools/index.js +11 -36
- package/mcp/tools/logout.js +32 -13
- package/mcp/tools/rcGetCallLogs.js +3 -20
- package/mcp/ui/App/App.tsx +358 -0
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -0
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -0
- package/mcp/ui/App/components/ConnectorList.tsx +82 -0
- package/mcp/ui/App/components/DebugPanel.tsx +43 -0
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -0
- package/mcp/ui/App/lib/callTool.ts +130 -0
- package/mcp/ui/App/lib/debugLog.ts +41 -0
- package/mcp/ui/App/lib/developerPortal.ts +111 -0
- package/mcp/ui/App/main.css +6 -0
- package/mcp/ui/App/root.tsx +13 -0
- package/mcp/ui/dist/index.html +53 -0
- package/mcp/ui/index.html +13 -0
- package/mcp/ui/package-lock.json +6356 -0
- package/mcp/ui/package.json +25 -0
- package/mcp/ui/tsconfig.json +26 -0
- package/mcp/ui/vite.config.ts +16 -0
- package/models/llmSessionModel.js +14 -0
- package/models/userModel.js +3 -0
- package/package.json +2 -2
- package/releaseNotes.json +24 -0
- package/test/handlers/auth.test.js +31 -0
- package/test/handlers/plugin.test.js +287 -0
- package/test/lib/util.test.js +379 -1
- package/test/mcp/tools/createCallLog.test.js +3 -3
- package/test/mcp/tools/doAuth.test.js +40 -303
- package/test/mcp/tools/findContactByName.test.js +3 -3
- package/test/mcp/tools/findContactByPhone.test.js +3 -3
- package/test/mcp/tools/getGoogleFilePicker.test.js +7 -7
- package/test/mcp/tools/getPublicConnectors.test.js +49 -70
- package/test/mcp/tools/logout.test.js +17 -11
- package/mcp/SupportedPlatforms.md +0 -12
- package/mcp/tools/collectAuthInfo.js +0 -91
- package/mcp/tools/setConnector.js +0 -69
- package/test/mcp/tools/collectAuthInfo.test.js +0 -234
- package/test/mcp/tools/setConnector.test.js +0 -177
package/index.js
CHANGED
|
@@ -3,8 +3,11 @@ const cors = require('cors')
|
|
|
3
3
|
const bodyParser = require('body-parser');
|
|
4
4
|
require('body-parser-xml')(bodyParser);
|
|
5
5
|
const dynamoose = require('dynamoose');
|
|
6
|
+
const Sequelize = require('sequelize');
|
|
7
|
+
const { DynamoDB } = require('@aws-sdk/client-dynamodb');
|
|
6
8
|
const axios = require('axios');
|
|
7
9
|
const { UserModel } = require('./models/userModel');
|
|
10
|
+
const { LlmSessionModel } = require('./models/llmSessionModel');
|
|
8
11
|
const { CallDownListModel } = require('./models/callDownListModel');
|
|
9
12
|
const { CallLogModel } = require('./models/callLogModel');
|
|
10
13
|
const { MessageLogModel } = require('./models/messageLogModel');
|
|
@@ -29,6 +32,7 @@ const mcpHandler = require('./mcp/mcpHandler');
|
|
|
29
32
|
const logger = require('./lib/logger');
|
|
30
33
|
const { DebugTracer } = require('./lib/debugTracer');
|
|
31
34
|
const s3ErrorLogReport = require('./lib/s3ErrorLogReport');
|
|
35
|
+
const pluginCore = require('./handlers/plugin');
|
|
32
36
|
const { handleDatabaseError } = require('./lib/errorHandler');
|
|
33
37
|
const { updateAuthSession } = require('./lib/authSession');
|
|
34
38
|
|
|
@@ -42,8 +46,13 @@ catch (e) {
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
// For using dynamodb in local env
|
|
49
|
+
// AWS SDK v3 requires a region even for local; ddb.local() omits it, so set manually.
|
|
45
50
|
if (process.env.DYNAMODB_LOCALHOST) {
|
|
46
|
-
dynamoose.aws.ddb.
|
|
51
|
+
dynamoose.aws.ddb.set(new DynamoDB({
|
|
52
|
+
endpoint: process.env.DYNAMODB_LOCALHOST,
|
|
53
|
+
region: 'local',
|
|
54
|
+
credentials: { accessKeyId: 'local', secretAccessKey: 'local' },
|
|
55
|
+
}));
|
|
47
56
|
}
|
|
48
57
|
// log axios requests
|
|
49
58
|
if (process.env.IS_PROD === 'false') {
|
|
@@ -58,12 +67,27 @@ async function initDB() {
|
|
|
58
67
|
if (!process.env.DISABLE_SYNC_DB_TABLE) {
|
|
59
68
|
logger.info('creating db tables if not exist...');
|
|
60
69
|
await UserModel.sync();
|
|
70
|
+
await LlmSessionModel.sync();
|
|
61
71
|
await CallLogModel.sync();
|
|
62
72
|
await MessageLogModel.sync();
|
|
63
73
|
await AdminConfigModel.sync();
|
|
64
74
|
await CacheModel.sync();
|
|
65
75
|
await CallDownListModel.sync();
|
|
66
76
|
await AccountDataModel.sync();
|
|
77
|
+
|
|
78
|
+
// if UserModel doesn't have hashedRcExtensionId column, add it
|
|
79
|
+
const queryInterface = UserModel.sequelize.getQueryInterface();
|
|
80
|
+
const userTableName = UserModel.getTableName();
|
|
81
|
+
const userTableSchema = await queryInterface.describeTable(userTableName);
|
|
82
|
+
if (!userTableSchema.hashedRcExtensionId) {
|
|
83
|
+
logger.info('adding hashedRcExtensionId column to users table...');
|
|
84
|
+
await queryInterface.addColumn(userTableName, 'hashedRcExtensionId', {
|
|
85
|
+
type: Sequelize.STRING,
|
|
86
|
+
allowNull: true,
|
|
87
|
+
});
|
|
88
|
+
await UserModel.sync();
|
|
89
|
+
logger.info('hashedRcExtensionId column added to users table');
|
|
90
|
+
}
|
|
67
91
|
}
|
|
68
92
|
}
|
|
69
93
|
|
|
@@ -756,7 +780,7 @@ function createCoreRouter() {
|
|
|
756
780
|
res.status(400).send(tracer ? tracer.wrapResponse('User not found') : 'User not found');
|
|
757
781
|
return;
|
|
758
782
|
}
|
|
759
|
-
const { userSettings } = await userCore.updateUserSettings({ user, userSettings: req.body.userSettings, platformName });
|
|
783
|
+
const { userSettings } = await userCore.updateUserSettings({ user, userSettings: req.body.userSettings, settingKeysToRemove: req.body.settingKeysToRemove || [], platformName });
|
|
760
784
|
res.status(200).send(tracer ? tracer.wrapResponse({ userSettings }) : { userSettings });
|
|
761
785
|
success = true;
|
|
762
786
|
}
|
|
@@ -861,6 +885,7 @@ function createCoreRouter() {
|
|
|
861
885
|
tokenUrl,
|
|
862
886
|
query: req.query,
|
|
863
887
|
proxyId: req.query.proxyId,
|
|
888
|
+
hashedRcExtensionId: hashedExtensionId,
|
|
864
889
|
isFromMCP
|
|
865
890
|
});
|
|
866
891
|
if (userInfo) {
|
|
@@ -947,7 +972,7 @@ function createCoreRouter() {
|
|
|
947
972
|
res.status(400).send(tracer ? tracer.wrapResponse('Missing api key') : 'Missing api key');
|
|
948
973
|
return;
|
|
949
974
|
}
|
|
950
|
-
const { userInfo, returnMessage } = await authCore.onApiKeyLogin({ platform, hostname, apiKey, proxyId, additionalInfo });
|
|
975
|
+
const { userInfo, returnMessage } = await authCore.onApiKeyLogin({ platform, hostname, apiKey, proxyId, rcAccountId: query.rcAccountId, hashedRcExtensionId: hashedExtensionId, additionalInfo });
|
|
951
976
|
if (userInfo) {
|
|
952
977
|
const jwtToken = jwt.generateJwt({
|
|
953
978
|
id: userInfo.id.toString(),
|
|
@@ -1323,7 +1348,7 @@ function createCoreRouter() {
|
|
|
1323
1348
|
}
|
|
1324
1349
|
const { id: userId, platform } = decodedToken;
|
|
1325
1350
|
platformName = platform;
|
|
1326
|
-
const { successful, logId, returnMessage, extraDataTracking, isRevokeUserSession } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1351
|
+
const { successful, logId, returnMessage, extraDataTracking, pluginAsyncTaskIds, isRevokeUserSession } = await logCore.createCallLog({ jwtToken, platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1327
1352
|
if (isRevokeUserSession) {
|
|
1328
1353
|
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1329
1354
|
success = false;
|
|
@@ -1332,7 +1357,7 @@ function createCoreRouter() {
|
|
|
1332
1357
|
if (extraDataTracking) {
|
|
1333
1358
|
extraData = extraDataTracking;
|
|
1334
1359
|
}
|
|
1335
|
-
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, returnMessage }) : { successful, logId, returnMessage });
|
|
1360
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, returnMessage, pluginAsyncTaskIds }) : { successful, logId, returnMessage, pluginAsyncTaskIds });
|
|
1336
1361
|
success = true;
|
|
1337
1362
|
}
|
|
1338
1363
|
}
|
|
@@ -1386,11 +1411,11 @@ function createCoreRouter() {
|
|
|
1386
1411
|
}
|
|
1387
1412
|
const { id: userId, platform } = decodedToken;
|
|
1388
1413
|
platformName = platform;
|
|
1389
|
-
const { successful, logId, updatedNote, returnMessage, extraDataTracking } = await logCore.updateCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1414
|
+
const { successful, logId, updatedNote, returnMessage, extraDataTracking, pluginAsyncTaskIds } = await logCore.updateCallLog({ jwtToken, platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1390
1415
|
if (extraDataTracking) {
|
|
1391
1416
|
extraData = extraDataTracking;
|
|
1392
1417
|
}
|
|
1393
|
-
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, updatedNote, returnMessage }) : { successful, logId, updatedNote, returnMessage });
|
|
1418
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, updatedNote, returnMessage, pluginAsyncTaskIds }) : { successful, logId, updatedNote, returnMessage, pluginAsyncTaskIds });
|
|
1394
1419
|
success = true;
|
|
1395
1420
|
}
|
|
1396
1421
|
else {
|
|
@@ -1941,6 +1966,55 @@ function createCoreRouter() {
|
|
|
1941
1966
|
});
|
|
1942
1967
|
});
|
|
1943
1968
|
|
|
1969
|
+
router.post('/pluginAsyncTask', async function (req, res) {
|
|
1970
|
+
const requestStartTime = new Date().getTime();
|
|
1971
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
1972
|
+
tracer?.trace('pluginAsyncTask:start', { query: req.query });
|
|
1973
|
+
let platformName = null;
|
|
1974
|
+
let success = false;
|
|
1975
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
1976
|
+
const { jwtToken } = req.query;
|
|
1977
|
+
try {
|
|
1978
|
+
if (!jwtToken) {
|
|
1979
|
+
tracer?.trace('pluginAsyncTask:noToken', {});
|
|
1980
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
1984
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
1985
|
+
if (!user) {
|
|
1986
|
+
tracer?.trace('pluginAsyncTask:userNotFound', {});
|
|
1987
|
+
res.status(400).send(tracer ? tracer.wrapResponse('User not found') : 'User not found');
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
const { asyncTaskIds } = req.body;
|
|
1991
|
+
const filteredTasksIds = asyncTaskIds.filter(taskId => taskId.startsWith(user.id));
|
|
1992
|
+
const tasks = await pluginCore.getPluginAsyncTasks({ asyncTaskIds: filteredTasksIds });
|
|
1993
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ tasks }) : { tasks });
|
|
1994
|
+
success = true;
|
|
1995
|
+
}
|
|
1996
|
+
catch (e) {
|
|
1997
|
+
console.log(`platform: ${platformName} \n${e.stack}`);
|
|
1998
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
1999
|
+
tracer?.traceError('pluginAsyncTask:error', e, { platform: platformName });
|
|
2000
|
+
success = false;
|
|
2001
|
+
}
|
|
2002
|
+
const requestEndTime = new Date().getTime();
|
|
2003
|
+
analytics.track({
|
|
2004
|
+
eventName: 'Plugin Async Task',
|
|
2005
|
+
interfaceName: 'pluginAsyncTask',
|
|
2006
|
+
connectorName: platformName,
|
|
2007
|
+
accountId: hashedAccountId,
|
|
2008
|
+
extensionId: hashedExtensionId,
|
|
2009
|
+
success,
|
|
2010
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
2011
|
+
userAgent,
|
|
2012
|
+
ip,
|
|
2013
|
+
author,
|
|
2014
|
+
eventAddedVia
|
|
2015
|
+
});
|
|
2016
|
+
});
|
|
2017
|
+
|
|
1944
2018
|
if (process.env.IS_PROD === 'false') {
|
|
1945
2019
|
router.post('/registerMockUser', async function (req, res) {
|
|
1946
2020
|
const secretKey = req.query.secretKey;
|
|
@@ -2057,24 +2131,32 @@ function createCoreRouter() {
|
|
|
2057
2131
|
});
|
|
2058
2132
|
});
|
|
2059
2133
|
|
|
2060
|
-
router.use('/mcp', (req, res, next) => {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
res.on('finish', () => {
|
|
2067
|
-
console.log(`[Response] Status: ${res.statusCode}`);
|
|
2068
|
-
console.log(`[Response] data: ${JSON.stringify(res.data)}`);
|
|
2069
|
-
});
|
|
2134
|
+
router.use('/mcp', (req, res, next) => {
|
|
2135
|
+
// Widget tool calls are unauthenticated — they come from the iframe
|
|
2136
|
+
// which has no access to the RC bearer token.
|
|
2137
|
+
if (req.path === '/widget-tool-call') {
|
|
2138
|
+
return next();
|
|
2139
|
+
}
|
|
2070
2140
|
|
|
2071
2141
|
const authHeader = req.headers.authorization;
|
|
2072
2142
|
const token = authHeader?.split(' ')[1]; // Remove "Bearer "
|
|
2073
|
-
// Allow
|
|
2074
|
-
// We only want to block the actual commands (POST).
|
|
2143
|
+
// Allow GET and OPTIONS (CORS preflight) to pass freely.
|
|
2075
2144
|
if (req.method === 'GET' || req.method === 'OPTIONS') {
|
|
2076
2145
|
return next();
|
|
2077
2146
|
}
|
|
2147
|
+
// Allow MCP discovery/handshake methods — these carry no user data and must be
|
|
2148
|
+
// reachable without auth so the ChatGPT developer portal can scan tools.
|
|
2149
|
+
const mcpMethod = req.body?.method;
|
|
2150
|
+
const UNAUTHENTICATED_MCP_METHODS = new Set([
|
|
2151
|
+
'initialize',
|
|
2152
|
+
'tools/list',
|
|
2153
|
+
'ping',
|
|
2154
|
+
'notifications/initialized',
|
|
2155
|
+
'notifications/cancelled',
|
|
2156
|
+
]);
|
|
2157
|
+
if (mcpMethod && UNAUTHENTICATED_MCP_METHODS.has(mcpMethod)) {
|
|
2158
|
+
return next();
|
|
2159
|
+
}
|
|
2078
2160
|
// SCENARIO 1: No Token provided. Kick off the OAuth flow.
|
|
2079
2161
|
if (!token) {
|
|
2080
2162
|
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
@@ -2084,9 +2166,7 @@ function createCoreRouter() {
|
|
|
2084
2166
|
// SCENARIO 2: Token provided. Verify it.
|
|
2085
2167
|
try {
|
|
2086
2168
|
next();
|
|
2087
|
-
} catch
|
|
2088
|
-
console.error("Token validation failed:", error.message);
|
|
2089
|
-
// Token is invalid or expired
|
|
2169
|
+
} catch {
|
|
2090
2170
|
res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
|
|
2091
2171
|
return res.status(401).send();
|
|
2092
2172
|
}
|
|
@@ -2109,6 +2189,19 @@ function createCoreRouter() {
|
|
|
2109
2189
|
await mcpHandler.handleMcpRequest(req, res);
|
|
2110
2190
|
});
|
|
2111
2191
|
|
|
2192
|
+
// Lightweight endpoint for widget tool calls (bypasses MCP protocol)
|
|
2193
|
+
router.options('/mcp/widget-tool-call', (req, res) => {
|
|
2194
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2195
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
2196
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
2197
|
+
res.status(200).end();
|
|
2198
|
+
});
|
|
2199
|
+
router.post('/mcp/widget-tool-call', async (req, res) => {
|
|
2200
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2201
|
+
res.setHeader('Content-Type', 'application/json');
|
|
2202
|
+
await mcpHandler.handleWidgetToolCall(req, res);
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2112
2205
|
return router;
|
|
2113
2206
|
}
|
|
2114
2207
|
|
package/lib/authSession.js
CHANGED
|
@@ -10,20 +10,29 @@ const AUTH_SESSION_PREFIX = 'auth-session';
|
|
|
10
10
|
const SESSION_EXPIRY_MINUTES = 5;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Create
|
|
13
|
+
* Create (or reset) an auth session.
|
|
14
|
+
* If a record already exists for the sessionId (e.g., user retries auth within
|
|
15
|
+
* the same ChatGPT conversation), it is reset to 'pending' so polling works
|
|
16
|
+
* correctly for the new attempt.
|
|
14
17
|
*/
|
|
15
18
|
async function createAuthSession(sessionId, data) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const id = `${AUTH_SESSION_PREFIX}-${sessionId}`;
|
|
20
|
+
const expiry = new Date(Date.now() + SESSION_EXPIRY_MINUTES * 60 * 1000);
|
|
21
|
+
const sessionData = { ...data, createdAt: new Date().toISOString() };
|
|
22
|
+
|
|
23
|
+
const existing = await CacheModel.findByPk(id);
|
|
24
|
+
if (existing) {
|
|
25
|
+
await existing.update({ status: 'pending', data: sessionData, expiry });
|
|
26
|
+
} else {
|
|
27
|
+
await CacheModel.create({
|
|
28
|
+
id,
|
|
29
|
+
cacheKey: AUTH_SESSION_PREFIX,
|
|
30
|
+
userId: sessionId,
|
|
31
|
+
status: 'pending',
|
|
32
|
+
data: sessionData,
|
|
33
|
+
expiry,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
/**
|
package/lib/callLogComposer.js
CHANGED
|
@@ -115,7 +115,7 @@ function composeCallLog(params) {
|
|
|
115
115
|
body = upsertCallRecording({ body, recordingLink, logFormat });
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
if (aiNote && (userSettings?.
|
|
118
|
+
if (aiNote && (userSettings?.addCallLogAiNote?.value ?? true)) {
|
|
119
119
|
body = upsertAiNote({ body, aiNote, logFormat });
|
|
120
120
|
}
|
|
121
121
|
|
package/lib/debugTracer.js
CHANGED
|
@@ -40,7 +40,7 @@ class DebugTracer {
|
|
|
40
40
|
*/
|
|
41
41
|
trace(methodName, data = {}, options = {}) {
|
|
42
42
|
const { includeStack = true, level = 'info' } = options;
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
const traceEntry = {
|
|
45
45
|
timestamp: new Date().toISOString(),
|
|
46
46
|
elapsed: Date.now() - this.startTime,
|
|
@@ -85,7 +85,7 @@ class DebugTracer {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const sensitiveFields = [
|
|
88
|
-
'accessToken', 'refreshToken', 'apiKey', 'password',
|
|
88
|
+
'accessToken', 'refreshToken', 'apiKey', 'password',
|
|
89
89
|
'secret', 'token', 'authorization', 'auth', 'key',
|
|
90
90
|
'credential', 'credentials', 'privateKey', 'clientSecret'
|
|
91
91
|
];
|
|
@@ -115,12 +115,30 @@ class DebugTracer {
|
|
|
115
115
|
return sanitizeRecursive(sanitized);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Builds a compact summary of all recorded actions, one entry per trace.
|
|
120
|
+
* Each entry contains the method name, log level, and elapsed time at the
|
|
121
|
+
* point the trace was recorded, making it easy to skim what happened without
|
|
122
|
+
* reading the full trace list.
|
|
123
|
+
* @returns {string[]} Array of human-readable action summary strings
|
|
124
|
+
*/
|
|
125
|
+
_buildActionSummary() {
|
|
126
|
+
return this.traces.map((t, i) => ({
|
|
127
|
+
index: i + 1,
|
|
128
|
+
timestamp: t.timestamp,
|
|
129
|
+
level: t.level.toUpperCase(),
|
|
130
|
+
method: t.methodName,
|
|
131
|
+
elapsedMs: t.elapsed
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
118
135
|
/**
|
|
119
136
|
* Gets the complete trace data for inclusion in response
|
|
120
137
|
* @returns {Object} Trace data object
|
|
121
138
|
*/
|
|
122
139
|
getTraceData() {
|
|
123
140
|
return {
|
|
141
|
+
sum: this._buildActionSummary(),
|
|
124
142
|
requestId: this.requestId,
|
|
125
143
|
totalDuration: `${Date.now() - this.startTime}ms`,
|
|
126
144
|
traceCount: this.traces.length,
|
package/lib/util.js
CHANGED
|
@@ -40,8 +40,7 @@ function secondsToHoursMinutesSeconds(seconds) {
|
|
|
40
40
|
function getMostRecentDate({ allDateValues }) {
|
|
41
41
|
var result = 0;
|
|
42
42
|
for (const date of allDateValues) {
|
|
43
|
-
if(!date)
|
|
44
|
-
{
|
|
43
|
+
if (!date) {
|
|
45
44
|
continue;
|
|
46
45
|
}
|
|
47
46
|
if (date > result) {
|
|
@@ -53,16 +52,34 @@ function getMostRecentDate({ allDateValues }) {
|
|
|
53
52
|
|
|
54
53
|
// media reader link: https://ringcentral.github.io/ringcentral-media-reader/?media=https://media.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/message-store/{messageId}/content/{contentId}
|
|
55
54
|
// platform media link: https://media.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/message-store/{messageId}/content/{contentId}
|
|
56
|
-
function getMediaReaderLinkByPlatformMediaLink(platformMediaLink){
|
|
57
|
-
if(!platformMediaLink){
|
|
55
|
+
function getMediaReaderLinkByPlatformMediaLink(platformMediaLink) {
|
|
56
|
+
if (!platformMediaLink) {
|
|
58
57
|
return null;
|
|
59
58
|
}
|
|
60
59
|
const encodedPlatformMediaLink = encodeURIComponent(platformMediaLink);
|
|
61
60
|
return `https://ringcentral.github.io/ringcentral-media-reader/?media=${encodedPlatformMediaLink}`;
|
|
62
61
|
}
|
|
63
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
|
+
|
|
64
80
|
exports.getTimeZone = getTimeZone;
|
|
65
81
|
exports.getHashValue = getHashValue;
|
|
66
82
|
exports.secondsToHoursMinutesSeconds = secondsToHoursMinutesSeconds;
|
|
67
83
|
exports.getMostRecentDate = getMostRecentDate;
|
|
68
84
|
exports.getMediaReaderLinkByPlatformMediaLink = getMediaReaderLinkByPlatformMediaLink;
|
|
85
|
+
exports.getPluginsFromUserSettings = getPluginsFromUserSettings;
|