@app-connect/core 1.7.10 → 1.7.11
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/developerPortal.js +43 -0
- package/connector/proxy/index.js +10 -3
- package/connector/registry.js +8 -6
- package/handlers/admin.js +44 -21
- package/handlers/auth.js +89 -67
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +4 -104
- package/handlers/disposition.js +4 -142
- package/handlers/log.js +172 -257
- package/handlers/user.js +19 -6
- package/index.js +213 -47
- package/lib/analytics.js +3 -1
- package/lib/authSession.js +68 -0
- package/lib/callLogComposer.js +498 -420
- package/lib/errorHandler.js +206 -0
- package/lib/jwt.js +2 -0
- package/lib/logger.js +190 -0
- package/lib/oauth.js +21 -10
- package/lib/ringcentral.js +2 -10
- package/lib/sharedSMSComposer.js +471 -0
- package/mcp/SupportedPlatforms.md +12 -0
- package/mcp/lib/validator.js +91 -0
- package/mcp/mcpHandler.js +166 -0
- package/mcp/tools/checkAuthStatus.js +90 -0
- package/mcp/tools/collectAuthInfo.js +86 -0
- package/mcp/tools/createCallLog.js +299 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +185 -0
- package/mcp/tools/findContactByName.js +87 -0
- package/mcp/tools/findContactByPhone.js +96 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getHelp.js +39 -0
- package/mcp/tools/getPublicConnectors.js +46 -0
- package/mcp/tools/index.js +58 -0
- package/mcp/tools/logout.js +63 -0
- package/mcp/tools/rcGetCallLogs.js +73 -0
- package/mcp/tools/setConnector.js +64 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/cacheModel.js +3 -0
- package/package.json +71 -70
- package/releaseNotes.json +12 -0
- package/test/handlers/log.test.js +6 -2
- package/test/lib/logger.test.js +206 -0
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/mcp/tools/collectAuthInfo.test.js +192 -0
- package/test/mcp/tools/createCallLog.test.js +412 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +363 -0
- package/test/mcp/tools/findContactByName.test.js +263 -0
- package/test/mcp/tools/findContactByPhone.test.js +284 -0
- package/test/mcp/tools/getCallLog.test.js +286 -0
- package/test/mcp/tools/getPublicConnectors.test.js +128 -0
- package/test/mcp/tools/logout.test.js +169 -0
- package/test/mcp/tools/setConnector.test.js +177 -0
- package/test/mcp/tools/updateCallLog.test.js +346 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const logger = require('./logger');
|
|
2
|
+
const errorMessage = require('./generalErrorMessage');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Centralized error handler for API operations
|
|
6
|
+
* Handles common error patterns (rate limits, auth errors, etc.)
|
|
7
|
+
*
|
|
8
|
+
* @param {Error} error - The error object
|
|
9
|
+
* @param {string} platform - Platform name (clio, bullhorn, etc.)
|
|
10
|
+
* @param {string} operation - Operation name (createCallLog, findContact, etc.)
|
|
11
|
+
* @param {Object} additionalContext - Additional logging context
|
|
12
|
+
* @returns {Object} Standardized error response
|
|
13
|
+
*/
|
|
14
|
+
function handleApiError(error, platform, operation, additionalContext = {}) {
|
|
15
|
+
const statusCode = error.response?.status ?? 'unknown';
|
|
16
|
+
|
|
17
|
+
// Log the error with full context
|
|
18
|
+
logger.error(`${operation} failed for platform ${platform}`, {
|
|
19
|
+
platform,
|
|
20
|
+
operation,
|
|
21
|
+
statusCode,
|
|
22
|
+
errorMessage: error.message,
|
|
23
|
+
errorStack: error.stack,
|
|
24
|
+
errorResponse: error.response?.data,
|
|
25
|
+
...additionalContext,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Rate limit error (429)
|
|
29
|
+
if (statusCode === 429) {
|
|
30
|
+
return {
|
|
31
|
+
successful: false,
|
|
32
|
+
returnMessage: errorMessage.rateLimitErrorMessage({ platform }),
|
|
33
|
+
extraDataTracking: {
|
|
34
|
+
statusCode,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Authorization/Authentication errors (400-409)
|
|
40
|
+
if (statusCode >= 400 && statusCode < 410) {
|
|
41
|
+
return {
|
|
42
|
+
successful: false,
|
|
43
|
+
returnMessage: errorMessage.authorizationErrorMessage({ platform }),
|
|
44
|
+
extraDataTracking: {
|
|
45
|
+
statusCode,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get operation-specific error message
|
|
51
|
+
const defaultErrorMessage = getOperationErrorMessage(operation, platform);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
successful: false,
|
|
55
|
+
returnMessage: defaultErrorMessage,
|
|
56
|
+
extraDataTracking: {
|
|
57
|
+
statusCode,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get operation-specific error message
|
|
64
|
+
* @param {string} operation - Operation name
|
|
65
|
+
* @param {string} platform - Platform name
|
|
66
|
+
* @returns {Object} Error message object
|
|
67
|
+
*/
|
|
68
|
+
function getOperationErrorMessage(operation, platform) {
|
|
69
|
+
const operationMessages = {
|
|
70
|
+
createCallLog: {
|
|
71
|
+
message: 'Error creating call log',
|
|
72
|
+
details: ['Please check if your account has permission to CREATE logs.'],
|
|
73
|
+
},
|
|
74
|
+
updateCallLog: {
|
|
75
|
+
message: 'Error updating call log',
|
|
76
|
+
details: [`Please check if the log entity still exists on ${platform} and your account has permission to EDIT logs.`],
|
|
77
|
+
},
|
|
78
|
+
getCallLog: {
|
|
79
|
+
message: 'Error getting call log',
|
|
80
|
+
details: ['Please check if your account has permission to READ logs.'],
|
|
81
|
+
},
|
|
82
|
+
createMessageLog: {
|
|
83
|
+
message: 'Error creating message log',
|
|
84
|
+
details: ['Please check if your account has permission to CREATE logs.'],
|
|
85
|
+
},
|
|
86
|
+
updateMessageLog: {
|
|
87
|
+
message: 'Error updating message log',
|
|
88
|
+
details: [`Please check if the log entity still exists on ${platform} and your account has permission to EDIT logs.`],
|
|
89
|
+
},
|
|
90
|
+
findContact: {
|
|
91
|
+
message: 'Error finding contact',
|
|
92
|
+
details: ['Please check if your account has permission to GET contacts.'],
|
|
93
|
+
},
|
|
94
|
+
createContact: {
|
|
95
|
+
message: 'Error creating contact',
|
|
96
|
+
details: ['Please check if your account has permission to CREATE contacts.'],
|
|
97
|
+
},
|
|
98
|
+
findContactWithName: {
|
|
99
|
+
message: 'Error searching contacts',
|
|
100
|
+
details: ['Please check if your account has permission to GET contacts.'],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const operationInfo = operationMessages[operation] || {
|
|
105
|
+
message: `Error performing ${operation}`,
|
|
106
|
+
details: ['Please check if your account has the necessary permissions.'],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
message: operationInfo.message,
|
|
111
|
+
messageType: 'warning',
|
|
112
|
+
details: [
|
|
113
|
+
{
|
|
114
|
+
title: 'Details',
|
|
115
|
+
items: operationInfo.details.map((detail, index) => ({
|
|
116
|
+
id: index + 1,
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: detail,
|
|
119
|
+
})),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
ttl: 5000,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle database errors
|
|
128
|
+
* @param {Error} error - The error object
|
|
129
|
+
* @param {string} operation - Database operation name
|
|
130
|
+
* @param {Object} context - Additional context
|
|
131
|
+
*/
|
|
132
|
+
function handleDatabaseError(error, operation, context = {}) {
|
|
133
|
+
logger.error(`Database operation failed: ${operation}`, {
|
|
134
|
+
operation,
|
|
135
|
+
errorMessage: error.message,
|
|
136
|
+
errorStack: error.stack,
|
|
137
|
+
...context,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
successful: false,
|
|
142
|
+
returnMessage: {
|
|
143
|
+
message: 'Database operation failed',
|
|
144
|
+
messageType: 'warning',
|
|
145
|
+
ttl: 5000,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Wrap async route handlers to catch errors
|
|
152
|
+
* Prevents unhandled promise rejections
|
|
153
|
+
* @param {Function} fn - Async route handler
|
|
154
|
+
* @returns {Function} Wrapped handler
|
|
155
|
+
*/
|
|
156
|
+
function asyncHandler(fn) {
|
|
157
|
+
return (req, res, next) => {
|
|
158
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Express error handling middleware
|
|
164
|
+
* Should be added after all routes
|
|
165
|
+
* @param {Error} err - Error object
|
|
166
|
+
* @param {Object} req - Express request
|
|
167
|
+
* @param {Object} res - Express response
|
|
168
|
+
* @param {Function} next - Express next function (required by Express signature)
|
|
169
|
+
*/
|
|
170
|
+
function errorMiddleware(err, req, res, next) { // eslint-disable-line no-unused-vars
|
|
171
|
+
const platform = req.platform || req.query?.platform || 'unknown';
|
|
172
|
+
const operation = req.route?.path || 'unknown';
|
|
173
|
+
|
|
174
|
+
logger.error('Request failed', {
|
|
175
|
+
platform,
|
|
176
|
+
operation,
|
|
177
|
+
method: req.method,
|
|
178
|
+
path: req.path,
|
|
179
|
+
statusCode: err.statusCode || 500,
|
|
180
|
+
error: err,
|
|
181
|
+
correlationId: req.correlationId,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Don't expose internal errors in production
|
|
185
|
+
const message = process.env.NODE_ENV === 'production'
|
|
186
|
+
? 'An internal error occurred'
|
|
187
|
+
: err.message;
|
|
188
|
+
|
|
189
|
+
res.status(err.statusCode || 500).json({
|
|
190
|
+
successful: false,
|
|
191
|
+
returnMessage: {
|
|
192
|
+
message,
|
|
193
|
+
messageType: 'error',
|
|
194
|
+
ttl: 5000,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
handleApiError,
|
|
201
|
+
handleDatabaseError,
|
|
202
|
+
asyncHandler,
|
|
203
|
+
errorMiddleware,
|
|
204
|
+
getOperationErrorMessage,
|
|
205
|
+
};
|
|
206
|
+
|
package/lib/jwt.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { sign, verify } = require('jsonwebtoken');
|
|
2
|
+
const logger = require('./logger');
|
|
2
3
|
|
|
3
4
|
function generateJwt(data) {
|
|
4
5
|
return sign(data, process.env.APP_SERVER_SECRET_KEY, { expiresIn: '120y' })
|
|
@@ -8,6 +9,7 @@ function decodeJwt(token) {
|
|
|
8
9
|
try {
|
|
9
10
|
return verify(token, process.env.APP_SERVER_SECRET_KEY);
|
|
10
11
|
} catch (e) {
|
|
12
|
+
logger.error('Error decoding JWT', { stack: e.stack });
|
|
11
13
|
return null;
|
|
12
14
|
}
|
|
13
15
|
}
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const util = require('util');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logger levels
|
|
5
|
+
*/
|
|
6
|
+
const LOG_LEVELS = {
|
|
7
|
+
ERROR: 0,
|
|
8
|
+
WARN: 1,
|
|
9
|
+
INFO: 2,
|
|
10
|
+
DEBUG: 3,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Color codes for terminal output
|
|
15
|
+
*/
|
|
16
|
+
const COLORS = {
|
|
17
|
+
ERROR: '\x1b[31m', // Red
|
|
18
|
+
WARN: '\x1b[33m', // Yellow
|
|
19
|
+
INFO: '\x1b[36m', // Cyan
|
|
20
|
+
DEBUG: '\x1b[90m', // Gray
|
|
21
|
+
RESET: '\x1b[0m',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class Logger {
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.level = this._getLogLevel(options.level || process.env.LOG_LEVEL || 'INFO');
|
|
27
|
+
this.isProd = process.env.NODE_ENV === 'production';
|
|
28
|
+
this.enableColors = !this.isProd && process.stdout.isTTY;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_getLogLevel(levelName) {
|
|
32
|
+
const upperLevel = levelName.toUpperCase();
|
|
33
|
+
return LOG_LEVELS[upperLevel] !== undefined ? LOG_LEVELS[upperLevel] : LOG_LEVELS.INFO;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_shouldLog(level) {
|
|
37
|
+
return LOG_LEVELS[level] <= this.level;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_formatMessage(level, message, context = {}) {
|
|
41
|
+
const timestamp = new Date().toISOString();
|
|
42
|
+
|
|
43
|
+
if (this.isProd) {
|
|
44
|
+
// Production: JSON format for log aggregation tools
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
timestamp,
|
|
47
|
+
level,
|
|
48
|
+
message,
|
|
49
|
+
...context,
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
// Development: Human-readable format with colors
|
|
53
|
+
const color = this.enableColors ? COLORS[level] : '';
|
|
54
|
+
const reset = this.enableColors ? COLORS.RESET : '';
|
|
55
|
+
const contextStr = Object.keys(context).length > 0
|
|
56
|
+
? '\n' + util.inspect(context, { depth: 4, colors: this.enableColors })
|
|
57
|
+
: '';
|
|
58
|
+
|
|
59
|
+
return `${color}[${timestamp}] [${level}]${reset} ${message}${contextStr}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_log(level, message, context = {}) {
|
|
64
|
+
if (!this._shouldLog(level)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const formattedMessage = this._formatMessage(level, message, context);
|
|
69
|
+
|
|
70
|
+
if (level === 'ERROR' || level === 'WARN') {
|
|
71
|
+
console.error(formattedMessage);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(formattedMessage);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Log error level messages
|
|
79
|
+
* @param {string} message - Log message
|
|
80
|
+
* @param {Object} context - Additional context (error, stack, platform, etc.)
|
|
81
|
+
*/
|
|
82
|
+
error(message, context = {}) {
|
|
83
|
+
// If context has an error object, extract useful information
|
|
84
|
+
let enrichedContext = context;
|
|
85
|
+
if (context.error instanceof Error) {
|
|
86
|
+
const { error, ...rest } = context;
|
|
87
|
+
enrichedContext = {
|
|
88
|
+
...rest,
|
|
89
|
+
errorMessage: error.message,
|
|
90
|
+
errorStack: error.stack,
|
|
91
|
+
errorResponse: error.response?.data,
|
|
92
|
+
errorStatus: error.response?.status,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this._log('ERROR', message, enrichedContext);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Log warning level messages
|
|
101
|
+
* @param {string} message - Log message
|
|
102
|
+
* @param {Object} context - Additional context
|
|
103
|
+
*/
|
|
104
|
+
warn(message, context = {}) {
|
|
105
|
+
this._log('WARN', message, context);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Log info level messages
|
|
110
|
+
* @param {string} message - Log message
|
|
111
|
+
* @param {Object} context - Additional context
|
|
112
|
+
*/
|
|
113
|
+
info(message, context = {}) {
|
|
114
|
+
this._log('INFO', message, context);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Log debug level messages
|
|
119
|
+
* @param {string} message - Log message
|
|
120
|
+
* @param {Object} context - Additional context
|
|
121
|
+
*/
|
|
122
|
+
debug(message, context = {}) {
|
|
123
|
+
this._log('DEBUG', message, context);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a child logger with default context
|
|
128
|
+
* Useful for adding request-specific or module-specific context
|
|
129
|
+
* @param {Object} defaultContext - Context to include in all logs
|
|
130
|
+
* @returns {Object} Child logger with bound context
|
|
131
|
+
*/
|
|
132
|
+
child(defaultContext = {}) {
|
|
133
|
+
return {
|
|
134
|
+
error: (message, context = {}) => this.error(message, { ...defaultContext, ...context }),
|
|
135
|
+
warn: (message, context = {}) => this.warn(message, { ...defaultContext, ...context }),
|
|
136
|
+
info: (message, context = {}) => this.info(message, { ...defaultContext, ...context }),
|
|
137
|
+
debug: (message, context = {}) => this.debug(message, { ...defaultContext, ...context }),
|
|
138
|
+
child: (additionalContext) => this.child({ ...defaultContext, ...additionalContext }),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Log API request/response for debugging
|
|
144
|
+
* @param {Object} options - Request details
|
|
145
|
+
*/
|
|
146
|
+
logApiRequest({ method, url, status, duration, platform, error }) {
|
|
147
|
+
const context = {
|
|
148
|
+
method,
|
|
149
|
+
url,
|
|
150
|
+
status,
|
|
151
|
+
duration: duration ? `${duration}ms` : undefined,
|
|
152
|
+
platform,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (error) {
|
|
156
|
+
this.error('API request failed', { ...context, error });
|
|
157
|
+
} else if (status >= 400) {
|
|
158
|
+
this.warn('API request returned error status', context);
|
|
159
|
+
} else {
|
|
160
|
+
this.debug('API request completed', context);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Log database query for debugging
|
|
166
|
+
* @param {Object} options - Query details
|
|
167
|
+
*/
|
|
168
|
+
logDatabaseQuery({ operation, table, duration, error }) {
|
|
169
|
+
const context = {
|
|
170
|
+
operation,
|
|
171
|
+
table,
|
|
172
|
+
duration: duration ? `${duration}ms` : undefined,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (error) {
|
|
176
|
+
this.error('Database query failed', { ...context, error });
|
|
177
|
+
} else {
|
|
178
|
+
this.debug('Database query completed', context);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Create singleton instance
|
|
184
|
+
const logger = new Logger();
|
|
185
|
+
|
|
186
|
+
// Export both the class and instance
|
|
187
|
+
module.exports = logger;
|
|
188
|
+
module.exports.Logger = Logger;
|
|
189
|
+
module.exports.LOG_LEVELS = LOG_LEVELS;
|
|
190
|
+
|
package/lib/oauth.js
CHANGED
|
@@ -3,7 +3,8 @@ const ClientOAuth2 = require('client-oauth2');
|
|
|
3
3
|
const moment = require('moment');
|
|
4
4
|
const { UserModel } = require('../models/userModel');
|
|
5
5
|
const connectorRegistry = require('../connector/registry');
|
|
6
|
-
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
const { handleDatabaseError } = require('./errorHandler');
|
|
7
8
|
// oauthApp strategy is default to 'code' which use credentials to get accessCode, then exchange for accessToken and refreshToken.
|
|
8
9
|
// To change to other strategies, please refer to: https://github.com/mulesoft-labs/js-client-oauth2
|
|
9
10
|
function getOAuthApp({ clientId, clientSecret, accessTokenUri, authorizationUri, redirectUri, scopes }) {
|
|
@@ -46,7 +47,7 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
46
47
|
overwrite: false
|
|
47
48
|
}
|
|
48
49
|
);
|
|
49
|
-
|
|
50
|
+
logger.info('lock created')
|
|
50
51
|
} catch (e) {
|
|
51
52
|
// If creation failed due to condition, a lock exists
|
|
52
53
|
if (e.name === 'ConditionalCheckFailedException' || e.__type === 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException') {
|
|
@@ -54,7 +55,7 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
54
55
|
if (!!lock?.ttl && moment(lock.ttl).unix() < now.unix()) {
|
|
55
56
|
// Try to delete expired lock and create a new one atomically
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
+
logger.info('lock expired.')
|
|
58
59
|
await lock.delete();
|
|
59
60
|
newLock = await Lock.create(
|
|
60
61
|
{
|
|
@@ -90,7 +91,7 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
90
91
|
throw new Error('Token lock timeout');
|
|
91
92
|
}
|
|
92
93
|
user = await UserModel.findByPk(user.id);
|
|
93
|
-
|
|
94
|
+
logger.info('locked. bypass')
|
|
94
95
|
return user;
|
|
95
96
|
}
|
|
96
97
|
} else {
|
|
@@ -100,20 +101,25 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
100
101
|
try {
|
|
101
102
|
const startRefreshTime = moment();
|
|
102
103
|
const token = oauthApp.createToken(user.accessToken, user.refreshToken);
|
|
103
|
-
|
|
104
|
+
logger.info('token refreshing...')
|
|
104
105
|
const { accessToken, refreshToken, expires } = await token.refresh();
|
|
105
106
|
user.accessToken = accessToken;
|
|
106
107
|
user.refreshToken = refreshToken;
|
|
107
108
|
user.tokenExpiry = expires;
|
|
109
|
+
try {
|
|
108
110
|
await user.save();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return handleDatabaseError(error, 'Error saving user');
|
|
114
|
+
}
|
|
109
115
|
if (newLock) {
|
|
110
116
|
const deletionStartTime = moment();
|
|
111
117
|
await newLock.delete();
|
|
112
118
|
const deletionEndTime = moment();
|
|
113
|
-
|
|
119
|
+
logger.info(`lock deleted in ${deletionEndTime.diff(deletionStartTime)}ms`)
|
|
114
120
|
}
|
|
115
121
|
const endRefreshTime = moment();
|
|
116
|
-
|
|
122
|
+
logger.info(`token refreshing finished in ${endRefreshTime.diff(startRefreshTime)}ms`)
|
|
117
123
|
}
|
|
118
124
|
catch (e) {
|
|
119
125
|
console.log('token refreshing failed', e.stack)
|
|
@@ -124,14 +130,19 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
124
130
|
}
|
|
125
131
|
// case: run withou token refresh lock
|
|
126
132
|
else {
|
|
127
|
-
|
|
133
|
+
logger.info('token refreshing...')
|
|
128
134
|
const token = oauthApp.createToken(user.accessToken, user.refreshToken);
|
|
129
135
|
const { accessToken, refreshToken, expires } = await token.refresh();
|
|
130
136
|
user.accessToken = accessToken;
|
|
131
137
|
user.refreshToken = refreshToken;
|
|
132
138
|
user.tokenExpiry = expires;
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
try {
|
|
140
|
+
await user.save();
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return handleDatabaseError(error, 'Error saving user');
|
|
144
|
+
}
|
|
145
|
+
logger.info('token refreshing finished')
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
}
|
package/lib/ringcentral.js
CHANGED
|
@@ -52,8 +52,6 @@ class RingCentral {
|
|
|
52
52
|
const {
|
|
53
53
|
expires_in,
|
|
54
54
|
refresh_token_expires_in,
|
|
55
|
-
scope,
|
|
56
|
-
endpoint_id, // do no save this field into db to reduce db size
|
|
57
55
|
...token
|
|
58
56
|
} = await response.json();
|
|
59
57
|
return {
|
|
@@ -79,8 +77,6 @@ class RingCentral {
|
|
|
79
77
|
const {
|
|
80
78
|
expires_in,
|
|
81
79
|
refresh_token_expires_in,
|
|
82
|
-
scope,
|
|
83
|
-
endpoint_id, // do no save this field into db to reduce db size
|
|
84
80
|
...newToken
|
|
85
81
|
} = await response.json();
|
|
86
82
|
return {
|
|
@@ -164,10 +160,6 @@ class RingCentral {
|
|
|
164
160
|
},
|
|
165
161
|
}, token);
|
|
166
162
|
const {
|
|
167
|
-
uri,
|
|
168
|
-
creationTime,
|
|
169
|
-
deliveryMode,
|
|
170
|
-
status, // do no save those field into db to reduce db size
|
|
171
163
|
...subscription
|
|
172
164
|
} = await response.json();
|
|
173
165
|
return subscription;
|
|
@@ -226,7 +218,7 @@ class RingCentral {
|
|
|
226
218
|
return response.json();
|
|
227
219
|
}
|
|
228
220
|
|
|
229
|
-
async getCallLogData({ extensionId = '~', token,
|
|
221
|
+
async getCallLogData({ extensionId = '~', token, timeFrom, timeTo }) {
|
|
230
222
|
let pageStart = 1;
|
|
231
223
|
let isFinalPage = false;
|
|
232
224
|
let callLogResponse = null;
|
|
@@ -247,7 +239,7 @@ class RingCentral {
|
|
|
247
239
|
}
|
|
248
240
|
return result;
|
|
249
241
|
}
|
|
250
|
-
async getSMSData({ extensionId = '~', token,
|
|
242
|
+
async getSMSData({ extensionId = '~', token, timeFrom, timeTo }) {
|
|
251
243
|
let pageStart = 1;
|
|
252
244
|
let isFinalPage = false;
|
|
253
245
|
let smsLogResponse = null;
|