@app-connect/core 0.0.2 → 1.5.8

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.
@@ -1,18 +1,13 @@
1
1
  const moment = require('moment-timezone');
2
2
  const { secondsToHoursMinutesSeconds } = require('./util');
3
3
  const adapterRegistry = require('../adapter/registry');
4
+ const { LOG_DETAILS_FORMAT_TYPE } = require('./constants');
4
5
 
5
6
  /**
6
7
  * Centralized call log composition module
7
8
  * Supports both plain text and HTML formats used across different CRM adapters
8
9
  */
9
10
 
10
- // Format types
11
- const FORMAT_TYPES = {
12
- PLAIN_TEXT: 'plainText',
13
- HTML: 'html'
14
- };
15
-
16
11
  /**
17
12
  * Compose call log details based on user settings and format type
18
13
  * @param {Object} params - Composition parameters
@@ -33,7 +28,7 @@ const FORMAT_TYPES = {
33
28
  */
34
29
  async function composeCallLog(params) {
35
30
  const {
36
- logFormat = FORMAT_TYPES.PLAIN_TEXT,
31
+ logFormat = LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
37
32
  existingBody = '',
38
33
  callLog,
39
34
  contactInfo,
@@ -117,67 +112,85 @@ async function composeCallLog(params) {
117
112
 
118
113
  function upsertCallAgentNote({ body, note, logFormat }) {
119
114
  if (!note) return body;
120
- if (logFormat === FORMAT_TYPES.HTML) {
115
+
116
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
121
117
  // HTML logFormat with proper Agent notes section handling
122
118
  const noteRegex = RegExp('<b>Agent notes</b>([\\s\\S]+?)Call details</b>');
123
119
  if (noteRegex.test(body)) {
124
120
  return body.replace(noteRegex, `<b>Agent notes</b><br>${note}<br><br><b>Call details</b>`);
125
121
  }
126
- else {
127
- return `<b>Agent notes</b><br>${note}<br><br><b>Call details</b><br>` + body;
122
+ return `<b>Agent notes</b><br>${note}<br><br><b>Call details</b><br>` + body;
123
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
124
+ // Markdown logFormat with proper Agent notes section handling
125
+ const noteRegex = /## Agent notes\n([\s\S]*?)\n## Call details/;
126
+ if (noteRegex.test(body)) {
127
+ return body.replace(noteRegex, `## Agent notes\n${note}\n\n## Call details`);
128
+ }
129
+ if (body.startsWith('## Call details')) {
130
+ return `## Agent notes\n${note}\n\n` + body;
128
131
  }
132
+ return `## Agent notes\n${note}\n\n## Call details\n` + body;
129
133
  } else {
130
134
  // Plain text logFormat - FIXED REGEX for multi-line notes with blank lines
131
135
  const noteRegex = /- (?:Note|Agent notes): ([\s\S]*?)(?=\n- [A-Z][a-zA-Z\s/]*:|\n$|$)/;
132
136
  if (noteRegex.test(body)) {
133
137
  return body.replace(noteRegex, `- Note: ${note}`);
134
- } else {
135
- return `- Note: ${note}\n` + body;
136
138
  }
139
+ return `- Note: ${note}\n` + body;
137
140
  }
138
141
  }
139
142
 
140
143
  function upsertCallSessionId({ body, id, logFormat }) {
141
144
  if (!id) return body;
142
145
 
143
- if (logFormat === FORMAT_TYPES.HTML) {
146
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
144
147
  // More flexible regex that handles both <li> wrapped and unwrapped content
145
148
  const idRegex = /(?:<li>)?<b>Session Id<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
146
149
  if (idRegex.test(body)) {
147
150
  return body.replace(idRegex, `<li><b>Session Id</b>: ${id}</li>`);
148
- } else {
149
- return body + `<li><b>Session Id</b>: ${id}</li>`;
150
151
  }
152
+ return body + `<li><b>Session Id</b>: ${id}</li>`;
153
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
154
+ // Markdown format: **Session Id**: value
155
+ const sessionIdRegex = /\*\*Session Id\*\*: [^\n]*\n*/;
156
+ if (sessionIdRegex.test(body)) {
157
+ return body.replace(sessionIdRegex, `**Session Id**: ${id}\n`);
158
+ }
159
+ return body + `**Session Id**: ${id}\n`;
151
160
  } else {
152
161
  // Match Session Id field and any trailing newlines, replace with single newline
153
162
  const sessionIdRegex = /- Session Id: [^\n]*\n*/;
154
163
  if (sessionIdRegex.test(body)) {
155
164
  return body.replace(sessionIdRegex, `- Session Id: ${id}\n`);
156
- } else {
157
- return body + `- Session Id: ${id}\n`;
158
165
  }
166
+ return body + `- Session Id: ${id}\n`;
159
167
  }
160
168
  }
161
169
 
162
170
  function upsertCallSubject({ body, subject, logFormat }) {
163
171
  if (!subject) return body;
164
172
 
165
- if (logFormat === FORMAT_TYPES.HTML) {
173
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
166
174
  // More flexible regex that handles both <li> wrapped and unwrapped content
167
175
  const subjectRegex = /(?:<li>)?<b>Summary<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
168
176
  if (subjectRegex.test(body)) {
169
177
  return body.replace(subjectRegex, `<li><b>Summary</b>: ${subject}</li>`);
170
- } else {
171
- return body + `<li><b>Summary</b>: ${subject}</li>`;
172
178
  }
179
+ return body + `<li><b>Summary</b>: ${subject}</li>`;
180
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
181
+ // Markdown format: **Summary**: value
182
+ const subjectRegex = /\*\*Summary\*\*: [^\n]*\n*/;
183
+ if (subjectRegex.test(body)) {
184
+ return body.replace(subjectRegex, `**Summary**: ${subject}\n`);
185
+ }
186
+ return body + `**Summary**: ${subject}\n`;
173
187
  } else {
174
188
  // Match Summary field and any trailing newlines, replace with single newline
175
189
  const subjectRegex = /- Summary: [^\n]*\n*/;
176
190
  if (subjectRegex.test(body)) {
177
191
  return body.replace(subjectRegex, `- Summary: ${subject}\n`);
178
- } else {
179
- return body + `- Summary: ${subject}\n`;
180
192
  }
193
+ return body + `- Summary: ${subject}\n`;
181
194
  }
182
195
  }
183
196
 
@@ -187,7 +200,7 @@ function upsertContactPhoneNumber({ body, phoneNumber, direction, logFormat }) {
187
200
  const label = direction === 'Outbound' ? 'Recipient' : 'Caller';
188
201
  let result = body;
189
202
 
190
- if (logFormat === FORMAT_TYPES.HTML) {
203
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
191
204
  // More flexible regex that handles both <li> wrapped and unwrapped content
192
205
  const phoneNumberRegex = new RegExp(`(?:<li>)?<b>${label} phone number</b>:\\s*([^<\\n]+)(?:</li>|(?=<|$))`, 'i');
193
206
  if (phoneNumberRegex.test(result)) {
@@ -195,6 +208,14 @@ function upsertContactPhoneNumber({ body, phoneNumber, direction, logFormat }) {
195
208
  } else {
196
209
  result += `<li><b>${label} phone number</b>: ${phoneNumber}</li>`;
197
210
  }
211
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
212
+ // Markdown format: **Contact Number**: value
213
+ const phoneNumberRegex = /\*\*Contact Number\*\*: [^\n]*\n*/;
214
+ if (phoneNumberRegex.test(result)) {
215
+ result = result.replace(phoneNumberRegex, `**Contact Number**: ${phoneNumber}\n`);
216
+ } else {
217
+ result += `**Contact Number**: ${phoneNumber}\n`;
218
+ }
198
219
  } else {
199
220
  // More flexible regex that handles both with and without newlines
200
221
  const phoneNumberRegex = /- Contact Number: ([^\n-]+)(?=\n-|\n|$)/;
@@ -225,7 +246,7 @@ function upsertCallDateTime({ body, startTime, timezoneOffset, logFormat }) {
225
246
  const formattedDateTime = momentTime.format('YYYY-MM-DD hh:mm:ss A');
226
247
  let result = body;
227
248
 
228
- if (logFormat === FORMAT_TYPES.HTML) {
249
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
229
250
  // More flexible regex that handles both <li> wrapped and unwrapped content
230
251
  const dateTimeRegex = /(?:<li>)?<b>Date\/time<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
231
252
  if (dateTimeRegex.test(result)) {
@@ -233,6 +254,14 @@ function upsertCallDateTime({ body, startTime, timezoneOffset, logFormat }) {
233
254
  } else {
234
255
  result += `<li><b>Date/time</b>: ${formattedDateTime}</li>`;
235
256
  }
257
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
258
+ // Markdown format: **Date/Time**: value
259
+ const dateTimeRegex = /\*\*Date\/Time\*\*: [^\n]*\n*/;
260
+ if (dateTimeRegex.test(result)) {
261
+ result = result.replace(dateTimeRegex, `**Date/Time**: ${formattedDateTime}\n`);
262
+ } else {
263
+ result += `**Date/Time**: ${formattedDateTime}\n`;
264
+ }
236
265
  } else {
237
266
  // Handle duplicated Date/Time entries and match complete date/time values
238
267
  const dateTimeRegex = /(?:- Date\/Time: [^-]*(?:-[^-]*)*)+/;
@@ -251,7 +280,7 @@ function upsertCallDuration({ body, duration, logFormat }) {
251
280
  const formattedDuration = secondsToHoursMinutesSeconds(duration);
252
281
  let result = body;
253
282
 
254
- if (logFormat === FORMAT_TYPES.HTML) {
283
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
255
284
  // More flexible regex that handles both <li> wrapped and unwrapped content
256
285
  const durationRegex = /(?:<li>)?<b>Duration<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
257
286
  if (durationRegex.test(result)) {
@@ -259,6 +288,14 @@ function upsertCallDuration({ body, duration, logFormat }) {
259
288
  } else {
260
289
  result += `<li><b>Duration</b>: ${formattedDuration}</li>`;
261
290
  }
291
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
292
+ // Markdown format: **Duration**: value
293
+ const durationRegex = /\*\*Duration\*\*: [^\n]*\n*/;
294
+ if (durationRegex.test(result)) {
295
+ result = result.replace(durationRegex, `**Duration**: ${formattedDuration}\n`);
296
+ } else {
297
+ result += `**Duration**: ${formattedDuration}\n`;
298
+ }
262
299
  } else {
263
300
  // More flexible regex that handles both with and without newlines
264
301
  const durationRegex = /- Duration: ([^\n-]+)(?=\n-|\n|$)/;
@@ -276,7 +313,7 @@ function upsertCallResult({ body, result, logFormat }) {
276
313
 
277
314
  let bodyResult = body;
278
315
 
279
- if (logFormat === FORMAT_TYPES.HTML) {
316
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
280
317
  // More flexible regex that handles both <li> wrapped and unwrapped content
281
318
  const resultRegex = /(?:<li>)?<b>Result<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
282
319
  if (resultRegex.test(bodyResult)) {
@@ -284,6 +321,14 @@ function upsertCallResult({ body, result, logFormat }) {
284
321
  } else {
285
322
  bodyResult += `<li><b>Result</b>: ${result}</li>`;
286
323
  }
324
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
325
+ // Markdown format: **Result**: value
326
+ const resultRegex = /\*\*Result\*\*: [^\n]*\n*/;
327
+ if (resultRegex.test(bodyResult)) {
328
+ bodyResult = bodyResult.replace(resultRegex, `**Result**: ${result}\n`);
329
+ } else {
330
+ bodyResult += `**Result**: ${result}\n`;
331
+ }
287
332
  } else {
288
333
  // More flexible regex that handles both with and without newlines
289
334
  const resultRegex = /- Result: ([^\n-]+)(?=\n-|\n|$)/;
@@ -297,12 +342,11 @@ function upsertCallResult({ body, result, logFormat }) {
297
342
  }
298
343
 
299
344
  function upsertCallRecording({ body, recordingLink, logFormat }) {
300
- // console.log({ m: "upsertCallRecording", recordingLink, hasBody: !!body, logFormat, bodyLength: body?.length });
301
345
  if (!recordingLink) return body;
302
346
 
303
347
  let result = body;
304
348
 
305
- if (logFormat === FORMAT_TYPES.HTML) {
349
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
306
350
  // More flexible regex that handles both <li> wrapped and unwrapped content
307
351
  const recordingLinkRegex = /(?:<li>)?<b>Call recording link<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
308
352
  if (recordingLink) {
@@ -326,6 +370,14 @@ function upsertCallRecording({ body, recordingLink, logFormat }) {
326
370
  }
327
371
  }
328
372
  }
373
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
374
+ // Markdown format: **Call recording link**: value
375
+ const recordingLinkRegex = /\*\*Call recording link\*\*: [^\n]*\n*/;
376
+ if (recordingLinkRegex.test(result)) {
377
+ result = result.replace(recordingLinkRegex, `**Call recording link**: ${recordingLink}\n`);
378
+ } else {
379
+ result += `**Call recording link**: ${recordingLink}\n`;
380
+ }
329
381
  } else {
330
382
  // Match recording link field and any trailing content, replace with single newline
331
383
  const recordingLinkRegex = /- Call recording link: [^\n]*\n*/;
@@ -347,7 +399,7 @@ function upsertAiNote({ body, aiNote, logFormat }) {
347
399
  const clearedAiNote = aiNote.replace(/\n+$/, '');
348
400
  let result = body;
349
401
 
350
- if (logFormat === FORMAT_TYPES.HTML) {
402
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
351
403
  const formattedAiNote = clearedAiNote.replace(/(?:\r\n|\r|\n)/g, '<br>');
352
404
  const aiNoteRegex = /<div><b>AI Note<\/b><br>(.+?)<\/div>/;
353
405
  if (aiNoteRegex.test(result)) {
@@ -355,6 +407,14 @@ function upsertAiNote({ body, aiNote, logFormat }) {
355
407
  } else {
356
408
  result += `<div><b>AI Note</b><br>${formattedAiNote}</div><br>`;
357
409
  }
410
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
411
+ // Markdown format: ### AI Note
412
+ const aiNoteRegex = /### AI Note\n([\s\S]*?)(?=\n### |\n$|$)/;
413
+ if (aiNoteRegex.test(result)) {
414
+ result = result.replace(aiNoteRegex, `### AI Note\n${clearedAiNote}\n`);
415
+ } else {
416
+ result += `### AI Note\n${clearedAiNote}\n`;
417
+ }
358
418
  } else {
359
419
  const aiNoteRegex = /- AI Note:([\s\S]*?)--- END/;
360
420
  if (aiNoteRegex.test(result)) {
@@ -371,7 +431,7 @@ function upsertTranscript({ body, transcript, logFormat }) {
371
431
 
372
432
  let result = body;
373
433
 
374
- if (logFormat === FORMAT_TYPES.HTML) {
434
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
375
435
  const formattedTranscript = transcript.replace(/(?:\r\n|\r|\n)/g, '<br>');
376
436
  const transcriptRegex = /<div><b>Transcript<\/b><br>(.+?)<\/div>/;
377
437
  if (transcriptRegex.test(result)) {
@@ -379,6 +439,14 @@ function upsertTranscript({ body, transcript, logFormat }) {
379
439
  } else {
380
440
  result += `<div><b>Transcript</b><br>${formattedTranscript}</div><br>`;
381
441
  }
442
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
443
+ // Markdown format: ### Transcript
444
+ const transcriptRegex = /### Transcript\n([\s\S]*?)(?=\n### |\n$|$)/;
445
+ if (transcriptRegex.test(result)) {
446
+ result = result.replace(transcriptRegex, `### Transcript\n${transcript}\n`);
447
+ } else {
448
+ result += `### Transcript\n${transcript}\n`;
449
+ }
382
450
  } else {
383
451
  const transcriptRegex = /- Transcript:([\s\S]*?)--- END/;
384
452
  if (transcriptRegex.test(result)) {
@@ -401,43 +469,9 @@ function getLogFormatType(platform) {
401
469
  return platformConfig?.logFormat;
402
470
  }
403
471
 
404
- /**
405
- * Create a specialized composition function for specific CRM requirements
406
- * @param {string} platform - CRM platform name
407
- * @returns {Function} Customized composition function
408
- */
409
- function createComposer(platform) {
410
- const logFormat = getLogFormatType(platform);
411
-
412
- return async function (params) {
413
- // Add platform-specific formatting
414
- if (logFormat === FORMAT_TYPES.HTML && platform === 'pipedrive') {
415
- // Pipedrive wraps call details in <ul> tags
416
- const composed = await composeCallLog({ ...params, logFormat });
417
- if (composed && !composed.includes('<ul>')) {
418
- return `<b>Call details</b><ul>${composed}</ul>`;
419
- }
420
- return composed;
421
- }
422
-
423
- if (logFormat === FORMAT_TYPES.HTML && platform === 'bullhorn') {
424
- // Bullhorn also wraps call details in <ul> tags
425
- const composed = await composeCallLog({ ...params, logFormat });
426
- if (composed && !composed.includes('<ul>')) {
427
- return `<b>Call details</b><ul>${composed}</ul>`;
428
- }
429
- return composed;
430
- }
431
-
432
- return composeCallLog({ ...params, logFormat });
433
- };
434
- }
435
-
436
472
  module.exports = {
437
473
  composeCallLog,
438
- createComposer,
439
474
  getLogFormatType,
440
- FORMAT_TYPES,
441
475
  // Export individual upsert functions for backward compatibility
442
476
  upsertCallAgentNote,
443
477
  upsertCallSessionId,
@@ -0,0 +1,9 @@
1
+ const LOG_DETAILS_FORMAT_TYPE = {
2
+ PLAIN_TEXT: 'text/plain',
3
+ HTML: 'text/html',
4
+ MARKDOWN: 'text/markdown'
5
+ };
6
+
7
+ module.exports = {
8
+ LOG_DETAILS_FORMAT_TYPE
9
+ };
package/lib/oauth.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  const ClientOAuth2 = require('client-oauth2');
3
- const { Lock } = require('../models/dynamo/lockSchema');
4
3
  const { UserModel } = require('../models/userModel');
5
4
  const adapterRegistry = require('../adapter/registry');
5
+ const dynamoose = require('dynamoose');
6
6
 
7
7
  // oauthApp strategy is default to 'code' which use credentials to get accessCode, then exchange for accessToken and refreshToken.
8
8
  // To change to other strategies, please refer to: https://github.com/mulesoft-labs/js-client-oauth2
@@ -31,33 +31,67 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
31
31
  }
32
32
  // Other CRMs
33
33
  if (user && user.accessToken && user.refreshToken && tokenExpiry.getTime() < (dateNow.getTime() + expiryBuffer)) {
34
+ let newLock;
34
35
  // case: use dynamoDB to manage token refresh lock
35
36
  if (process.env.USE_TOKEN_REFRESH_LOCK === 'true') {
36
- let lock = await Lock.get({ userId: user.id });
37
- let newLock;
38
- if (!!lock?.ttl && lock.ttl < dateNow.getTime()) {
39
- await lock.delete();
40
- lock = null;
41
- }
42
- if (lock) {
43
- let processTime = 0;
44
- while (!!lock && processTime < tokenLockTimeout) {
45
- await new Promise(resolve => setTimeout(resolve, 2000)); // wait for 2 seconds
46
- processTime += 2;
47
- lock = await Lock.get({ userId: user.id });
48
- }
49
- // Timeout -> let users try another time
50
- if (processTime >= tokenLockTimeout) {
51
- throw new Error('Token lock timeout');
37
+ const { Lock } = require('../models/dynamo/lockSchema');
38
+ // Try to atomically create lock only if it doesn't exist
39
+ try {
40
+ newLock = await Lock.create(
41
+ {
42
+ userId: user.id,
43
+ ttl: dateNow.getTime() + 1000 * 30
44
+ },
45
+ {
46
+ condition: new dynamoose.Condition().where('userId').not().exists()
47
+ }
48
+ );
49
+ console.log('lock created')
50
+ } catch (e) {
51
+ // If creation failed due to condition, a lock exists
52
+ if (e.name === 'ConditionalCheckFailedException' || e.__type === 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException') {
53
+ let lock = await Lock.get({ userId: user.id });
54
+ if (!!lock?.ttl && lock.ttl < dateNow.getTime()) {
55
+ // Try to delete expired lock and create a new one atomically
56
+ try {
57
+ await lock.delete();
58
+ newLock = await Lock.create(
59
+ { userId: user.id },
60
+ {
61
+ condition: new dynamoose.Condition().where('userId').not().exists()
62
+ }
63
+ );
64
+ } catch (e2) {
65
+ if (e2.name === 'ConditionalCheckFailedException' || e2.__type === 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException') {
66
+ // Another process created a lock between our delete and create
67
+ lock = await Lock.get({ userId: user.id });
68
+ } else {
69
+ throw e2;
70
+ }
71
+ }
72
+ }
73
+
74
+ if (lock && !newLock) {
75
+ let processTime = 0;
76
+ while (!!lock && processTime < tokenLockTimeout) {
77
+ await new Promise(resolve => setTimeout(resolve, 2000)); // wait for 2 seconds
78
+ processTime += 2;
79
+ lock = await Lock.get({ userId: user.id });
80
+ }
81
+ // Timeout -> let users try another time
82
+ if (processTime >= tokenLockTimeout) {
83
+ throw new Error('Token lock timeout');
84
+ }
85
+ user = await UserModel.findByPk(user.id);
86
+ console.log('locked. bypass')
87
+ return user;
88
+ }
89
+ } else {
90
+ throw e;
52
91
  }
53
- user = await UserModel.findByPk(user.id);
54
- }
55
- else {
56
- newLock = await Lock.create({
57
- userId: user.id
58
- });
59
92
  }
60
93
  const token = oauthApp.createToken(user.accessToken, user.refreshToken);
94
+ console.log('token refreshing...')
61
95
  const { accessToken, refreshToken, expires } = await token.refresh();
62
96
  user.accessToken = accessToken;
63
97
  user.refreshToken = refreshToken;
@@ -66,15 +100,18 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
66
100
  if (newLock) {
67
101
  await newLock.delete();
68
102
  }
103
+ console.log('token refreshing finished')
69
104
  }
70
105
  // case: run withou token refresh lock
71
106
  else {
107
+ console.log('token refreshing...')
72
108
  const token = oauthApp.createToken(user.accessToken, user.refreshToken);
73
109
  const { accessToken, refreshToken, expires } = await token.refresh();
74
110
  user.accessToken = accessToken;
75
111
  user.refreshToken = refreshToken;
76
112
  user.tokenExpiry = expires;
77
113
  await user.save();
114
+ console.log('token refreshing finished')
78
115
  }
79
116
 
80
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app-connect/core",
3
- "version": "0.0.2",
3
+ "version": "1.5.8",
4
4
  "description": "RingCentral App Connect Core",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -35,6 +35,12 @@
35
35
  "tz-lookup": "^6.1.25",
36
36
  "ua-parser-js": "^1.0.38"
37
37
  },
38
+ "scripts": {
39
+ "test": "jest",
40
+ "test:watch": "jest --watch",
41
+ "test:coverage": "jest --coverage",
42
+ "test:ci": "jest --ci --coverage --watchAll=false"
43
+ },
38
44
  "devDependencies": {
39
45
  "@eslint/js": "^9.22.0",
40
46
  "@octokit/rest": "^19.0.5",
package/releaseNotes.json CHANGED
@@ -1,4 +1,32 @@
1
1
  {
2
+ "1.5.8": {
3
+ "global": [
4
+ {
5
+ "type": "Fix",
6
+ "description": "- Error on showing a window-size warning message"
7
+ },
8
+ {
9
+ "type": "Fix",
10
+ "description": "- Disconnected users shown as connected"
11
+ },
12
+ {
13
+ "type": "Better",
14
+ "description": "- Every 5min, retroatively check recording links at pending state and update them to existing call logs"
15
+ },
16
+ {
17
+ "type": "Better",
18
+ "description": "- More stable user session"
19
+ },
20
+ {
21
+ "type": "Better",
22
+ "description": "- User settings sync message suppressed"
23
+ },
24
+ {
25
+ "type": "Better",
26
+ "description": "- Webpage embed (Click-to-dial and Quick-access-button) urls setting is renamed to 'Enabled domains' under General -> Appearance"
27
+ }
28
+ ]
29
+ },
2
30
  "1.5.7": {
3
31
  "global": [
4
32
  {