@app-connect/core 1.5.8 → 1.6.4

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/lib/constants.js CHANGED
@@ -1,9 +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
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
9
  };
package/lib/encode.js CHANGED
@@ -1,30 +1,30 @@
1
- const crypto = require('crypto');
2
-
3
- function getCipherKey() {
4
- if (!process.env.APP_SERVER_SECRET_KEY) {
5
- throw new Error('APP_SERVER_SECRET_KEY is not defined');
6
- }
7
- if (process.env.APP_SERVER_SECRET_KEY.length < 32) {
8
- // pad secret key with spaces if it is less than 32 bytes
9
- return process.env.APP_SERVER_SECRET_KEY.padEnd(32, ' ');
10
- }
11
- if (process.env.APP_SERVER_SECRET_KEY.length > 32) {
12
- // truncate secret key if it is more than 32 bytes
13
- return process.env.APP_SERVER_SECRET_KEY.slice(0, 32);
14
- }
15
- return process.env.APP_SERVER_SECRET_KEY;
16
- }
17
-
18
- function encode(data) {
19
- const cipher = crypto.createCipheriv('aes-256-cbc', getCipherKey(), Buffer.alloc(16, 0));
20
- return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
21
- }
22
-
23
- function decoded(encryptedData) {
24
- const decipher = crypto.createDecipheriv('aes-256-cbc', getCipherKey(), Buffer.alloc(16, 0));
25
- return decipher.update(encryptedData, 'hex', 'utf8') + decipher.final('utf8');
26
- }
27
-
28
-
29
- exports.encode = encode;
30
- exports.decoded = decoded;
1
+ const crypto = require('crypto');
2
+
3
+ function getCipherKey() {
4
+ if (!process.env.APP_SERVER_SECRET_KEY) {
5
+ throw new Error('APP_SERVER_SECRET_KEY is not defined');
6
+ }
7
+ if (process.env.APP_SERVER_SECRET_KEY.length < 32) {
8
+ // pad secret key with spaces if it is less than 32 bytes
9
+ return process.env.APP_SERVER_SECRET_KEY.padEnd(32, ' ');
10
+ }
11
+ if (process.env.APP_SERVER_SECRET_KEY.length > 32) {
12
+ // truncate secret key if it is more than 32 bytes
13
+ return process.env.APP_SERVER_SECRET_KEY.slice(0, 32);
14
+ }
15
+ return process.env.APP_SERVER_SECRET_KEY;
16
+ }
17
+
18
+ function encode(data) {
19
+ const cipher = crypto.createCipheriv('aes-256-cbc', getCipherKey(), Buffer.alloc(16, 0));
20
+ return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
21
+ }
22
+
23
+ function decoded(encryptedData) {
24
+ const decipher = crypto.createDecipheriv('aes-256-cbc', getCipherKey(), Buffer.alloc(16, 0));
25
+ return decipher.update(encryptedData, 'hex', 'utf8') + decipher.final('utf8');
26
+ }
27
+
28
+
29
+ exports.encode = encode;
30
+ exports.decoded = decoded;
@@ -1,42 +1,42 @@
1
- function rateLimitErrorMessage({ platform }) {
2
- return {
3
- message: `Rate limit exceeded`,
4
- messageType: 'warning',
5
- details: [
6
- {
7
- title: 'Details',
8
- items: [
9
- {
10
- id: '1',
11
- type: 'text',
12
- text: `You have exceeded the maximum number of requests allowed by ${platform}. Please try again in the next minute. If the problem persists please contact support.`
13
- }
14
- ]
15
- }
16
- ],
17
- ttl: 5000
18
- }
19
- }
20
-
21
- function authorizationErrorMessage({ platform }) {
22
- return {
23
- message: `Authorization error`,
24
- messageType: 'warning',
25
- details: [
26
- {
27
- title: 'Details',
28
- items: [
29
- {
30
- id: '1',
31
- type: 'text',
32
- text: `It seems like there's something wrong with your authorization of ${platform}. Please Logout and then Connect your ${platform} account within this extension.`
33
- }
34
- ]
35
- }
36
- ],
37
- ttl: 5000
38
- }
39
- }
40
-
41
- exports.rateLimitErrorMessage = rateLimitErrorMessage;
1
+ function rateLimitErrorMessage({ platform }) {
2
+ return {
3
+ message: `Rate limit exceeded`,
4
+ messageType: 'warning',
5
+ details: [
6
+ {
7
+ title: 'Details',
8
+ items: [
9
+ {
10
+ id: '1',
11
+ type: 'text',
12
+ text: `You have exceeded the maximum number of requests allowed by ${platform}. Please try again in the next minute. If the problem persists please contact support.`
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ ttl: 5000
18
+ }
19
+ }
20
+
21
+ function authorizationErrorMessage({ platform }) {
22
+ return {
23
+ message: `Authorization error`,
24
+ messageType: 'warning',
25
+ details: [
26
+ {
27
+ title: 'Details',
28
+ items: [
29
+ {
30
+ id: '1',
31
+ type: 'text',
32
+ text: `It seems like there's something wrong with your authorization of ${platform}. Please Logout and then Connect your ${platform} account within this extension.`
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ ttl: 5000
38
+ }
39
+ }
40
+
41
+ exports.rateLimitErrorMessage = rateLimitErrorMessage;
42
42
  exports.authorizationErrorMessage = authorizationErrorMessage;
package/lib/jwt.js CHANGED
@@ -1,16 +1,16 @@
1
- const { sign, verify } = require('jsonwebtoken');
2
-
3
- function generateJwt(data) {
4
- return sign(data, process.env.APP_SERVER_SECRET_KEY, { expiresIn: '120y' })
5
- }
6
-
7
- function decodeJwt(token) {
8
- try {
9
- return verify(token, process.env.APP_SERVER_SECRET_KEY);
10
- } catch (e) {
11
- return null;
12
- }
13
- }
14
-
15
- exports.generateJwt = generateJwt;
16
- exports.decodeJwt = decodeJwt;
1
+ const { sign, verify } = require('jsonwebtoken');
2
+
3
+ function generateJwt(data) {
4
+ return sign(data, process.env.APP_SERVER_SECRET_KEY, { expiresIn: '120y' })
5
+ }
6
+
7
+ function decodeJwt(token) {
8
+ try {
9
+ return verify(token, process.env.APP_SERVER_SECRET_KEY);
10
+ } catch (e) {
11
+ return null;
12
+ }
13
+ }
14
+
15
+ exports.generateJwt = generateJwt;
16
+ exports.decodeJwt = decodeJwt;
package/lib/oauth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  const ClientOAuth2 = require('client-oauth2');
3
+ const moment = require('moment');
3
4
  const { UserModel } = require('../models/userModel');
4
5
  const adapterRegistry = require('../adapter/registry');
5
6
  const dynamoose = require('dynamoose');
@@ -18,10 +19,10 @@ function getOAuthApp({ clientId, clientSecret, accessTokenUri, authorizationUri,
18
19
  }
19
20
 
20
21
 
21
- async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10) {
22
- const dateNow = new Date();
23
- const tokenExpiry = new Date(user.tokenExpiry);
24
- const expiryBuffer = 1000 * 60 * 2; // 2 minutes => 120000ms
22
+ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20) {
23
+ const now = moment();
24
+ const tokenExpiry = moment(user.tokenExpiry);
25
+ const expiryBuffer = 2; // 2 minutes
25
26
  // Special case: Bullhorn
26
27
  if (user.platform) {
27
28
  const platformModule = adapterRegistry.getAdapter(user.platform);
@@ -29,21 +30,21 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
29
30
  return platformModule.checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout);
30
31
  }
31
32
  }
32
- // Other CRMs
33
- if (user && user.accessToken && user.refreshToken && tokenExpiry.getTime() < (dateNow.getTime() + expiryBuffer)) {
34
- let newLock;
33
+ // Other CRMs - check if token will expire within the buffer time
34
+ if (user && user.accessToken && user.refreshToken && tokenExpiry.isBefore(now.clone().add(expiryBuffer, 'minutes'))) {
35
35
  // case: use dynamoDB to manage token refresh lock
36
- if (process.env.USE_TOKEN_REFRESH_LOCK === 'true') {
36
+ if (adapterRegistry.getManifest('default')?.platforms?.[user.platform]?.auth?.useTokenRefreshLock) {
37
+ let newLock;
37
38
  const { Lock } = require('../models/dynamo/lockSchema');
38
39
  // Try to atomically create lock only if it doesn't exist
39
40
  try {
40
41
  newLock = await Lock.create(
41
- {
42
+ {
42
43
  userId: user.id,
43
- ttl: dateNow.getTime() + 1000 * 30
44
- },
45
- {
46
- condition: new dynamoose.Condition().where('userId').not().exists()
44
+ ttl: now.unix() + 30
45
+ },
46
+ {
47
+ overwrite: false
47
48
  }
48
49
  );
49
50
  console.log('lock created')
@@ -51,14 +52,18 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
51
52
  // If creation failed due to condition, a lock exists
52
53
  if (e.name === 'ConditionalCheckFailedException' || e.__type === 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException') {
53
54
  let lock = await Lock.get({ userId: user.id });
54
- if (!!lock?.ttl && lock.ttl < dateNow.getTime()) {
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 {
58
+ console.log('lock expired.')
57
59
  await lock.delete();
58
60
  newLock = await Lock.create(
59
- { userId: user.id },
60
- {
61
- condition: new dynamoose.Condition().where('userId').not().exists()
61
+ {
62
+ userId: user.id,
63
+ ttl: now.unix() + 30
64
+ },
65
+ {
66
+ overwrite: false
62
67
  }
63
68
  );
64
69
  } catch (e2) {
@@ -70,12 +75,15 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
70
75
  }
71
76
  }
72
77
  }
73
-
78
+
74
79
  if (lock && !newLock) {
75
80
  let processTime = 0;
81
+ let delay = 500; // Start with 500ms
82
+ const maxDelay = 8000; // Cap at 8 seconds
76
83
  while (!!lock && processTime < tokenLockTimeout) {
77
- await new Promise(resolve => setTimeout(resolve, 2000)); // wait for 2 seconds
78
- processTime += 2;
84
+ await new Promise(resolve => setTimeout(resolve, delay));
85
+ processTime += delay / 1000; // Convert to seconds for comparison
86
+ delay = Math.min(delay * 2, maxDelay); // Exponential backoff with cap
79
87
  lock = await Lock.get({ userId: user.id });
80
88
  }
81
89
  // Timeout -> let users try another time
@@ -90,6 +98,7 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
90
98
  throw e;
91
99
  }
92
100
  }
101
+ const startRefreshTime = moment();
93
102
  const token = oauthApp.createToken(user.accessToken, user.refreshToken);
94
103
  console.log('token refreshing...')
95
104
  const { accessToken, refreshToken, expires } = await token.refresh();
@@ -98,9 +107,13 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 10)
98
107
  user.tokenExpiry = expires;
99
108
  await user.save();
100
109
  if (newLock) {
110
+ const deletionStartTime = moment();
101
111
  await newLock.delete();
112
+ const deletionEndTime = moment();
113
+ console.log(`lock deleted in ${deletionEndTime.diff(deletionStartTime)}ms`)
102
114
  }
103
- console.log('token refreshing finished')
115
+ const endRefreshTime = moment();
116
+ console.log(`token refreshing finished in ${endRefreshTime.diff(startRefreshTime)}ms`)
104
117
  }
105
118
  // case: run withou token refresh lock
106
119
  else {
package/lib/util.js CHANGED
@@ -1,40 +1,43 @@
1
-
2
- const tzlookup = require('tz-lookup');
3
- const { State } = require('country-state-city');
4
- const crypto = require('crypto');
5
-
6
- function getTimeZone(countryCode, stateCode) {
7
- const state = State.getStateByCodeAndCountry(stateCode, countryCode);
8
- if (!state) {
9
- return 'Unknown timezone';
10
- }
11
- const timezone = tzlookup(state.latitude, state.longitude);
12
- return timezone;
13
- }
14
-
15
-
16
- function getHashValue(string, secretKey) {
17
- return crypto.createHash('sha256').update(
18
- `${string}:${secretKey}`
19
- ).digest('hex');
20
- }
21
-
22
- function secondsToHoursMinutesSeconds(seconds) {
23
- // If not a number, return the input directly
24
- if (isNaN(seconds)) {
25
- return seconds;
26
- }
27
- const hours = Math.floor(seconds / 3600);
28
- const hoursString = hours > 0 ? `${hours} ${hours > 1 ? 'hours' : 'hour'}` : '';
29
- const minutes = Math.floor((seconds % 3600) / 60);
30
- const minutesString = minutes > 0 ? `${minutes} ${minutes > 1 ? 'minutes' : 'minute'}` : '';
31
- const remainingSeconds = seconds % 60;
32
- const secondsString = remainingSeconds > 0 ? `${remainingSeconds} ${remainingSeconds > 1 ? 'seconds' : 'second'}` : '';
33
- const resultString = [hoursString, minutesString, secondsString].filter(Boolean).join(', ');
34
- return resultString;
35
- }
36
-
37
- exports.getTimeZone = getTimeZone;
38
- exports.getHashValue = getHashValue;
39
- exports.secondsToHoursMinutesSeconds = secondsToHoursMinutesSeconds;
40
-
1
+
2
+ const tzlookup = require('tz-lookup');
3
+ const { State } = require('country-state-city');
4
+ const crypto = require('crypto');
5
+
6
+ function getTimeZone(countryCode, stateCode) {
7
+ const state = State.getStateByCodeAndCountry(stateCode, countryCode);
8
+ if (!state) {
9
+ return 'Unknown timezone';
10
+ }
11
+ const timezone = tzlookup(state.latitude, state.longitude);
12
+ return timezone;
13
+ }
14
+
15
+
16
+ function getHashValue(string, secretKey) {
17
+ return crypto.createHash('sha256').update(
18
+ `${string}:${secretKey}`
19
+ ).digest('hex');
20
+ }
21
+
22
+ function secondsToHoursMinutesSeconds(seconds) {
23
+ // If not a number, return the input directly
24
+ if (isNaN(seconds)) {
25
+ return seconds;
26
+ }
27
+ const hours = Math.floor(seconds / 3600);
28
+ const hoursString = hours > 0 ? `${hours} ${hours > 1 ? 'hours' : 'hour'}` : '';
29
+ const minutes = Math.floor((seconds % 3600) / 60);
30
+ const minutesString = minutes > 0 ? `${minutes} ${minutes > 1 ? 'minutes' : 'minute'}` : '';
31
+ const remainingSeconds = seconds % 60;
32
+ const secondsString = remainingSeconds > 0 ? `${remainingSeconds} ${remainingSeconds > 1 ? 'seconds' : 'second'}` : '';
33
+ if (!hoursString && !minutesString && !secondsString) {
34
+ return '0 seconds';
35
+ }
36
+ const resultString = [hoursString, minutesString, secondsString].filter(Boolean).join(', ');
37
+ return resultString;
38
+ }
39
+
40
+ exports.getTimeZone = getTimeZone;
41
+ exports.getHashValue = getHashValue;
42
+ exports.secondsToHoursMinutesSeconds = secondsToHoursMinutesSeconds;
43
+
@@ -1,17 +1,17 @@
1
- const Sequelize = require('sequelize');
2
- const { sequelize } = require('./sequelize');
3
-
4
- // Model for User data
5
- exports.AdminConfigModel = sequelize.define('adminConfigs', {
6
- // hashed rc account ID
7
- id: {
8
- type: Sequelize.STRING,
9
- primaryKey: true,
10
- },
11
- userSettings: {
12
- type: Sequelize.JSON
13
- },
14
- customAdapter: {
15
- type: Sequelize.JSON
16
- }
17
- });
1
+ const Sequelize = require('sequelize');
2
+ const { sequelize } = require('./sequelize');
3
+
4
+ // Model for User data
5
+ exports.AdminConfigModel = sequelize.define('adminConfigs', {
6
+ // hashed rc account ID
7
+ id: {
8
+ type: Sequelize.STRING,
9
+ primaryKey: true,
10
+ },
11
+ userSettings: {
12
+ type: Sequelize.JSON
13
+ },
14
+ customAdapter: {
15
+ type: Sequelize.JSON
16
+ }
17
+ });
@@ -1,23 +1,23 @@
1
- const Sequelize = require('sequelize');
2
- const { sequelize } = require('./sequelize');
3
-
4
- // Model for cache data
5
- exports.CacheModel = sequelize.define('cache', {
6
- // id = {userId}-{cacheKey}
7
- id: {
8
- type: Sequelize.STRING,
9
- primaryKey: true,
10
- },
11
- status: {
12
- type: Sequelize.STRING,
13
- },
14
- userId: {
15
- type: Sequelize.STRING,
16
- },
17
- cacheKey: {
18
- type: Sequelize.STRING,
19
- },
20
- expiry: {
21
- type: Sequelize.DATE
22
- }
23
- });
1
+ const Sequelize = require('sequelize');
2
+ const { sequelize } = require('./sequelize');
3
+
4
+ // Model for cache data
5
+ exports.CacheModel = sequelize.define('cache', {
6
+ // id = {userId}-{cacheKey}
7
+ id: {
8
+ type: Sequelize.STRING,
9
+ primaryKey: true,
10
+ },
11
+ status: {
12
+ type: Sequelize.STRING,
13
+ },
14
+ userId: {
15
+ type: Sequelize.STRING,
16
+ },
17
+ cacheKey: {
18
+ type: Sequelize.STRING,
19
+ },
20
+ expiry: {
21
+ type: Sequelize.DATE
22
+ }
23
+ });
@@ -1,27 +1,27 @@
1
- const Sequelize = require('sequelize');
2
- const { sequelize } = require('./sequelize');
3
-
4
- // Model for User data
5
- exports.CallLogModel = sequelize.define('callLogs', {
6
- // callId
7
- id: {
8
- type: Sequelize.STRING,
9
- primaryKey: true,
10
- },
11
- sessionId: {
12
- type: Sequelize.STRING,
13
- primaryKey: true,
14
- },
15
- platform: {
16
- type: Sequelize.STRING,
17
- },
18
- thirdPartyLogId: {
19
- type: Sequelize.STRING,
20
- },
21
- userId: {
22
- type: Sequelize.STRING,
23
- },
24
- contactId: {
25
- type: Sequelize.STRING,
26
- }
27
- });
1
+ const Sequelize = require('sequelize');
2
+ const { sequelize } = require('./sequelize');
3
+
4
+ // Model for User data
5
+ exports.CallLogModel = sequelize.define('callLogs', {
6
+ // callId
7
+ id: {
8
+ type: Sequelize.STRING,
9
+ primaryKey: true,
10
+ },
11
+ sessionId: {
12
+ type: Sequelize.STRING,
13
+ primaryKey: true,
14
+ },
15
+ platform: {
16
+ type: Sequelize.STRING,
17
+ },
18
+ thirdPartyLogId: {
19
+ type: Sequelize.STRING,
20
+ },
21
+ userId: {
22
+ type: Sequelize.STRING,
23
+ },
24
+ contactId: {
25
+ type: Sequelize.STRING,
26
+ }
27
+ });
@@ -1,25 +1,25 @@
1
- const dynamoose = require('dynamoose');
2
-
3
- const lockSchema = new dynamoose.Schema({
4
- userId: {
5
- type: String,
6
- hashKey: true
7
- },
8
- ttl: {
9
- type: Number
10
- }
11
- });
12
-
13
- const tableOptions = {
14
- prefix: process.env.DYNAMODB_TABLE_PREFIX,
15
- expires: 60 // 60 seconds
16
- };
17
-
18
- if (process.env.NODE_ENV === 'production') {
19
- tableOptions.create = false;
20
- tableOptions.waitForActive = false;
21
- }
22
-
23
- const Lock = dynamoose.model('-token-refresh-lock', lockSchema, tableOptions);
24
-
1
+ const dynamoose = require('dynamoose');
2
+
3
+ const lockSchema = new dynamoose.Schema({
4
+ userId: {
5
+ type: String,
6
+ hashKey: true
7
+ },
8
+ ttl: {
9
+ type: Number
10
+ }
11
+ });
12
+
13
+ const tableOptions = {
14
+ prefix: process.env.DYNAMODB_TABLE_PREFIX,
15
+ expires: 60 // 60 seconds
16
+ };
17
+
18
+ if (process.env.NODE_ENV === 'production') {
19
+ tableOptions.create = false;
20
+ tableOptions.waitForActive = false;
21
+ }
22
+
23
+ const Lock = dynamoose.model('-token-refresh-lock', lockSchema, tableOptions);
24
+
25
25
  exports.Lock = Lock;
@@ -0,0 +1,30 @@
1
+ const dynamoose = require('dynamoose');
2
+
3
+ const noteCacheSchema = new dynamoose.Schema({
4
+ sessionId: {
5
+ type: String,
6
+ hashKey: true,
7
+ },
8
+ note: {
9
+ type: String,
10
+ required: true,
11
+ },
12
+ ttl: {
13
+ type: Number,
14
+ required: true,
15
+ },
16
+ });
17
+
18
+ const tableOptions = {
19
+ prefix: process.env.DYNAMODB_TABLE_PREFIX,
20
+ expires: 60 // 60 seconds
21
+ };
22
+
23
+ if (process.env.NODE_ENV === 'production') {
24
+ tableOptions.create = false;
25
+ tableOptions.waitForActive = false;
26
+ }
27
+
28
+ const NoteCache = dynamoose.model('-note-cache', noteCacheSchema, tableOptions);
29
+
30
+ exports.NoteCache = NoteCache;
@@ -1,25 +1,25 @@
1
- const Sequelize = require('sequelize');
2
- const { sequelize } = require('./sequelize');
3
-
4
- // Model for User data
5
- exports.MessageLogModel = sequelize.define('messageLogs', {
6
- id: {
7
- type: Sequelize.STRING,
8
- primaryKey: true,
9
- },
10
- platform: {
11
- type: Sequelize.STRING,
12
- },
13
- conversationId: {
14
- type: Sequelize.STRING,
15
- },
16
- conversationLogId:{
17
- type: Sequelize.STRING,
18
- },
19
- thirdPartyLogId: {
20
- type: Sequelize.STRING,
21
- },
22
- userId: {
23
- type: Sequelize.STRING,
24
- }
25
- });
1
+ const Sequelize = require('sequelize');
2
+ const { sequelize } = require('./sequelize');
3
+
4
+ // Model for User data
5
+ exports.MessageLogModel = sequelize.define('messageLogs', {
6
+ id: {
7
+ type: Sequelize.STRING,
8
+ primaryKey: true,
9
+ },
10
+ platform: {
11
+ type: Sequelize.STRING,
12
+ },
13
+ conversationId: {
14
+ type: Sequelize.STRING,
15
+ },
16
+ conversationLogId:{
17
+ type: Sequelize.STRING,
18
+ },
19
+ thirdPartyLogId: {
20
+ type: Sequelize.STRING,
21
+ },
22
+ userId: {
23
+ type: Sequelize.STRING,
24
+ }
25
+ });