@tiledesk/tiledesk-voice-twilio-connector 0.1.28 → 0.2.0-rc3

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.
Files changed (50) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +44 -0
  3. package/index.js +7 -1562
  4. package/package.json +23 -22
  5. package/src/app.js +146 -0
  6. package/src/config/index.js +32 -0
  7. package/src/controllers/VoiceController.js +488 -0
  8. package/src/controllers/VoiceController.original.js +811 -0
  9. package/src/middlewares/httpLogger.js +31 -0
  10. package/src/models/KeyValueStore.js +78 -0
  11. package/src/routes/manageApp.js +298 -0
  12. package/src/routes/voice.js +22 -0
  13. package/src/services/AiService.js +219 -0
  14. package/src/services/AiService.sdk.js +367 -0
  15. package/src/services/IntegrationService.js +74 -0
  16. package/src/services/MessageService.js +133 -0
  17. package/src/services/README_SDK.md +107 -0
  18. package/src/services/SessionService.js +143 -0
  19. package/src/services/SpeechService.js +134 -0
  20. package/src/services/TiledeskMessageBuilder.js +135 -0
  21. package/src/services/TwilioService.js +122 -0
  22. package/src/services/UploadService.js +78 -0
  23. package/src/services/channels/TiledeskChannel.js +269 -0
  24. package/{tiledesk → src/services/channels}/VoiceChannel.js +17 -56
  25. package/src/services/clients/TiledeskSubscriptionClient.js +78 -0
  26. package/src/services/index.js +45 -0
  27. package/src/services/translators/TiledeskTwilioTranslator.js +509 -0
  28. package/{tiledesk/TiledeskTwilioTranslator.js → src/services/translators/TiledeskTwilioTranslator.original.js} +119 -212
  29. package/src/utils/fileUtils.js +24 -0
  30. package/src/utils/logger.js +32 -0
  31. package/{tiledesk → src/utils}/utils-message.js +6 -21
  32. package/logs/app.log +0 -3082
  33. package/routes/manageApp.js +0 -419
  34. package/tiledesk/KVBaseMongo.js +0 -101
  35. package/tiledesk/TiledeskChannel.js +0 -363
  36. package/tiledesk/TiledeskSubscriptionClient.js +0 -135
  37. package/tiledesk/fileUtils.js +0 -55
  38. package/tiledesk/services/AiService.js +0 -230
  39. package/tiledesk/services/IntegrationService.js +0 -81
  40. package/tiledesk/services/UploadService.js +0 -88
  41. /package/{winston.js → src/config/logger.js} +0 -0
  42. /package/{tiledesk → src}/services/voiceEventEmitter.js +0 -0
  43. /package/{template → src/template}/configure.html +0 -0
  44. /package/{template → src/template}/css/configure.css +0 -0
  45. /package/{template → src/template}/css/error.css +0 -0
  46. /package/{template → src/template}/css/style.css +0 -0
  47. /package/{template → src/template}/error.html +0 -0
  48. /package/{tiledesk → src/utils}/constants.js +0 -0
  49. /package/{tiledesk → src/utils}/errors.js +0 -0
  50. /package/{tiledesk → src/utils}/utils.js +0 -0
@@ -1,363 +0,0 @@
1
- const axios = require("axios").default;
2
- const jwt = require("jsonwebtoken");
3
- const { v4: uuidv4 } = require("uuid");
4
- const { promisify } = require('util');
5
- const fs = require('fs');
6
-
7
- /*UTILS*/
8
- const utils = require('./utils-message.js')
9
- const fileUtils = require('./fileUtils.js')
10
- const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
11
- const MESSAGE_TYPE_MINE = require('./constants').MESSAGE_TYPE_MINE
12
- const MESSAGE_TYPE_OTHERS = require('./constants').MESSAGE_TYPE_OTHERS
13
- const CHANNEL_NAME = require('./constants').CHANNEL_NAME
14
-
15
- const Redlock = require('redlock');
16
- const redisLock = require("redis-lock")
17
-
18
-
19
- const winston = require("../winston");
20
-
21
- class TiledeskChannel {
22
-
23
-
24
- constructor(config) {
25
-
26
- if (!config) {
27
- throw new Error("[TiledeskChannel] config is mandatory");
28
- }
29
- if (!config.API_URL) {
30
- throw new Error("[TiledeskChannel] config.API_URL is mandatory");
31
- }
32
- if (!config.redis_client) {
33
- throw new Error("[TiledeskChannel] config.redis_client is mandatory");
34
- }
35
-
36
- this.log = false;
37
- if (config.log) {
38
- this.log = config.log;
39
- }
40
-
41
- this.API_URL = config.API_URL;
42
- this.redis_client = config.redis_client
43
-
44
-
45
- this.redlock = new Redlock([this.redis_client], {
46
- // The expected clock drift; for more details see:
47
- // http://redis.io/topics/distlock
48
- driftFactor: 0.01, // multiplied by lock ttl to determine drift time
49
-
50
- // The max number of times Redlock will attempt to lock a resource
51
- // before erroring.
52
- retryCount: 10,
53
-
54
- // the time in ms between attempts
55
- retryDelay: 1000, // time in ms
56
-
57
- });
58
-
59
- this.redlock.on("error", (error) => {
60
- // Ignore cases where a resource is explicitly marked as locked on a client.
61
- console.log('ERRRRRR', error)
62
- });
63
-
64
- this.lock = require("redis-lock")(this.redis_client);
65
-
66
-
67
- let _project_id = config.project_id
68
- this.setProjectId = function(project_id) { _project_id = project_id; }
69
- this.getProjectId = function() { return _project_id; }
70
- }
71
-
72
-
73
-
74
- async signIn(user_id, settings) {
75
- // ani = calling phone number
76
-
77
- winston.debug('[TiledeskChannel] sigIn settings', settings)
78
-
79
- let payload = {
80
- _id: CHANNEL_NAME + '-' + user_id,
81
- firstname: user_id,
82
- lastname: "",
83
- phone: user_id,
84
- sub: "userexternal",
85
- aud: "https://tiledesk.com/subscriptions/" + settings.subscriptionId,
86
- };
87
- let customToken = jwt.sign(payload, settings.secret);
88
-
89
- return await axios({
90
- url: this.API_URL + "/auth/signInWithCustomToken",
91
- headers: {
92
- "Content-Type": "application/json",
93
- Authorization: "JWT " + customToken,
94
- },
95
- data: {},
96
- method: "POST",
97
- }).then( async (response) => {
98
- if (!response.data) {
99
- return null;
100
- }
101
- //response.data.token = await this.fixToken(response.data.token);
102
- let token = await this.fixToken(response.data.token);
103
-
104
- let data = {
105
- token: token,
106
- _id: response.data.user._id
107
- };
108
-
109
- return data
110
-
111
- })
112
- .catch((err) => {
113
- winston.error("[TiledeskChannel] sign in error: ", err.response);
114
- return null;
115
- });
116
- }
117
-
118
- async generateConversation(ani, callId){
119
- return "support-group-" + this.getProjectId() + "-" + ani + "-" + CHANNEL_NAME + "-" + callId;
120
- }
121
-
122
- async getConversation(ani, callId, token) {
123
- let new_request_id = await this.generateConversation(ani, callId)
124
-
125
- return axios({
126
- url: this.API_URL + "/" + this.getProjectId() + "/requests/me?channel=" + CHANNEL_NAME,
127
- //url: this.API_URL + "/" + this.getProjectId() + "/requests/me",
128
- headers: {
129
- "Content-Type": "application/json",
130
- Authorization: token,
131
- },
132
- method: "GET",
133
- }).then((response) => {
134
- let request_id;
135
- if (response.data.requests[0]) {
136
- request_id = response.data.requests[0].request_id;
137
- winston.debug("[TiledeskChannel] use already opened conversation: ", request_id);
138
- } else {
139
- request_id = new_request_id;
140
- winston.debug("[TiledeskChannel] use new conversation: ", request_id);
141
- }
142
- return request_id;
143
- }).catch((err) => {
144
- winston.error("[TiledeskChannel] get requests error: ", err);
145
- return null;
146
- });
147
- }
148
-
149
- async getDepartments(token) {
150
-
151
- return new Promise( async (resolve, reject) => {
152
- await axios({
153
- url: this.API_URL + "/" + this.getProjectId() + "/departments/allstatus",
154
- headers: {
155
- 'Content-Type': 'application/json',
156
- 'Authorization': token
157
- },
158
- method: 'GET'
159
- }).then((response) => {
160
- winston.debug("[TiledeskChannel] get departments response.data: ", response.data)
161
- resolve(response.data)
162
- }).catch((err) => {
163
- winston.error("[TiledeskChannel] get departments error");
164
- reject(err)
165
- })
166
- })
167
- }
168
-
169
- async send(tiledeskMessage, token, conversation_id) {
170
-
171
- return axios({
172
- url: this.API_URL + `/${this.getProjectId()}/requests/${conversation_id}/messages`,
173
- headers: {
174
- 'Content-Type': 'application/json',
175
- 'Authorization': token
176
- },
177
- data: tiledeskMessage,
178
- method: 'POST'
179
- }).then((response) => {
180
- winston.debug("[TiledeskChannel] send message response: ", response.data);
181
- return response.data;
182
- }).catch((err) => {
183
- winston.error("[TiledeskChannel] send message: ", err.response?.data);
184
- return null;
185
- })
186
-
187
- }
188
-
189
-
190
- /** ADD MESSAGE TO REDIS QUEUE **/
191
- async addMessageToQueue(message){
192
-
193
- /*SKIP INFO MESSAGES*/
194
- if(utils.messageType(TYPE_MESSAGE.INFO, message)){
195
- winston.debug("> SKIPPING INFO message: " + JSON.stringify(message) );
196
- return;
197
- }
198
-
199
- /*SKIP CURRENT USER MESSAGES*/
200
- if (message.sender.indexOf(CHANNEL_NAME) > -1) {
201
- winston.debug("> SKIPPING ECHO message: " + JSON.stringify(message) );
202
- return;
203
- }
204
-
205
- winston.debug("> SAVE message TO QUEUE: " + JSON.stringify(message) );
206
- /*SAVE MESSAGE TO QUEUE WITH KET tiledesk:queue:+conversation_id*/
207
- let conversation_id = message.recipient
208
- let redis_data = [ message ]
209
- //get redis data for current conversation
210
- /*const queue = await this.redis_client.get('tiledesk:queue:'+conversation_id);
211
- if (queue) {
212
- redis_data = JSON.parse(queue); //overrride default value with the stored one
213
- console.log('redis_dataaaaa lenght before -->', redis_data.length, new Date())
214
- redis_data.push(message) //push new received message
215
- console.log('redis_dataaaaa lenght after -->', redis_data.length, new Date())
216
- }
217
- //save data to redis
218
- await this.redis_client.set('tiledesk:queue:'+conversation_id, JSON.stringify(redis_data), {'EX': 86400});
219
- */
220
-
221
- //push element to queue list and set expiration time
222
- await this.redis_client.rPush('tiledesk:queue:'+conversation_id, JSON.stringify(message));
223
- this.redis_client.expire('tiledesk:queue:'+conversation_id, 86400)
224
- await this.redis_client.publish('tiledesk:conversation:'+conversation_id, JSON.stringify(message));
225
-
226
- }
227
-
228
-
229
- /** GET MESSAGES FROM REDIS QUEUE LIST **/
230
- async getMessagesFromQueue(conversation_id){
231
- let messages= [];
232
-
233
- //get redis data list for current conversation
234
- const queue = await this.redis_client.lRange('tiledesk:queue:'+conversation_id, 0, -1);
235
- if(queue){
236
- messages = queue.map(item => JSON.parse(item));
237
- if(messages.length > 0){
238
- return messages
239
- }
240
- }
241
- return messages;
242
- }
243
-
244
- /** PUBLISH MESSAGE TO REDIS TOPIC **/
245
- async publishMessageToTopic(message){
246
-
247
- /*SKIP INFO MESSAGES*/
248
- if(utils.messageType(TYPE_MESSAGE.INFO, message)){
249
- winston.debug("> SKIPPING INFO message: " + JSON.stringify(message) );
250
- return;
251
- }
252
-
253
- /*SKIP CURRENT USER MESSAGES*/
254
- if (message.sender.indexOf("vxml") > -1) {
255
- winston.debug("> SKIPPING ECHO message: " + JSON.stringify(message) );
256
- return;
257
- }
258
-
259
- winston.debug("> SAVE message TO QUEUE: " + JSON.stringify(message) );
260
-
261
- let conversation_id = message.recipient
262
- //PUBLISH MESSAGE TO REDIS TOPIC WITH KET tiledesk:queue:+conversation_id
263
- await this.redis_client.publish('tiledesk:conversation:'+conversation_id, JSON.stringify(message));
264
- }
265
-
266
- /** SUBSCRIBE TO REDIS TOPIC */
267
- async subscribeToTopic(conversation_id){
268
- const topic = `tiledesk:conversation:${conversation_id}`;
269
- // console.log("subscribeToTopic: " + topic);
270
-
271
- // duplichi il client principale
272
- const subscriber = this.redis_client.duplicate();
273
- await subscriber.connect();
274
-
275
- let resolved = false; // flag per assicurarsi che risolva solo una volta
276
-
277
- return new Promise((resolve, reject) => {
278
- const cleanup = async () => {
279
- try {
280
- await subscriber.unsubscribe(topic);
281
- await subscriber.quit();
282
- } catch (err) {
283
- // ignora errori di chiusura
284
- }
285
- };
286
-
287
- subscriber.subscribe(topic, async (message) => {
288
- if (resolved) return; // se già risolto, ignora altri messaggi
289
- resolved = true;
290
-
291
- try {
292
- const json = JSON.parse(message);
293
- resolve(json);
294
- } catch (err) {
295
- reject(err);
296
- } finally {
297
- cleanup(); // chiudi il subscriber appena arriva il primo messaggio
298
- }
299
- }).catch(async (err) => {
300
- if (!resolved) {
301
- resolved = true;
302
- reject(err);
303
- await cleanup();
304
- }
305
- });
306
- });
307
- }
308
-
309
- /** REMOVE MESSAGE FROM REDIS QUEUE LIST **/
310
- async removeMessageFromQueue(conversation_id, message_id){
311
- let redis_data = []
312
-
313
- //get redis data list for current conversation
314
- const queue = await this.redis_client.lRange('tiledesk:queue:'+conversation_id, 0, -1);
315
- if (queue) {
316
- redis_data = queue.map(item => JSON.parse(item)); //overrride default value with the stored one
317
- redis_data = redis_data.filter((msg) => msg._id === message_id)
318
-
319
- //remove current message from list (key, element, count=0 -> remove all elements equal to value)
320
- await this.redis_client.lRem('tiledesk:queue:'+conversation_id, 0, JSON.stringify(redis_data[0]))
321
- }
322
-
323
- }
324
-
325
- /** CLEAR QUEUE FROM REDIS **/
326
- async clearQueue(conversation_id){
327
- if(conversation_id){
328
- await this.redis_client.del('tiledesk:queue:'+conversation_id)
329
- }
330
- }
331
-
332
-
333
-
334
-
335
- async generateWaitTdMessage(ani, delayTime){
336
-
337
-
338
- return {
339
- text: '',
340
- senderFullname: ani,
341
- attributes: {
342
- commands:[
343
- { type: 'wait', time: delayTime}
344
- ]
345
- },
346
- channel: { name: CHANNEL_NAME }
347
- }
348
- }
349
-
350
-
351
- async fixToken(token) {
352
- let index = token.lastIndexOf("JWT ");
353
- if (index != -1) {
354
- let new_token = token.substring(index + 4);
355
- return "JWT " + new_token;
356
- } else {
357
- return "JWT " + token;
358
- }
359
- }
360
-
361
- }
362
-
363
- module.exports = { TiledeskChannel };
@@ -1,135 +0,0 @@
1
- const axios = require("axios").default;
2
- const winston = require('../winston')
3
-
4
- class TiledeskSubscriptionClient {
5
-
6
- /**
7
- * Constructor for TiledeskSubscriptionClient
8
- *
9
- * @example
10
- * const { TiledeskSubscriptionClient } = require('tiledesk-subscription-client');
11
- * const tdClient = new TiledeskSubscriptionClient({API_URL: tiledeskApiUrl, token: jwt_token, log: log});
12
- * @param {Object} config JSON configuration.
13
- * @param {string} config.API_URL Mandatory. The Tiledesk api url.
14
- * @param {string} config.token Optional. Token required for authentication.
15
- * @param {boolean} config.log Optional. If true HTTP requests are logged.
16
- */
17
- constructor(config) {
18
- if (!config) {
19
- throw new Error('[TiledeskSubscriptionClient] config is mandatory');
20
- }
21
-
22
- if (!config.API_URL) {
23
- throw new Error('[TiledeskSubscriptionClient] config.API_URL is mandatory');
24
- }
25
-
26
- this.project_id = config.project_id
27
- this.API_URL = config.API_URL;
28
- this.token = config.token;
29
- this.config = config;
30
- this.log = false;
31
- if (config.log) {
32
- this.log = config.log;
33
- }
34
-
35
- }
36
-
37
- async subscribe(subscription_info, callback) {
38
- const URL = this.API_URL + `/${this.project_id}/subscriptions`;
39
- const HTTPREQUEST = {
40
- url: URL,
41
- headers: {
42
- 'Content-Type': 'application/json',
43
- 'Authorization': this.token
44
- },
45
- json: subscription_info,
46
- method: 'POST'
47
- };
48
- let promise = new Promise((resolve, reject) => {
49
- TiledeskSubscriptionClient.myrequest(
50
- HTTPREQUEST,
51
- function(err, resbody) {
52
- if (err) {
53
- if (callback) {
54
- callback(err);
55
- }
56
- reject(err);
57
- }
58
- else {
59
- if (callback) {
60
- callback(null, resbody);
61
- }
62
- winston.debug("[TiledeskSubscriptionClient] Subscribed");
63
- resolve(resbody);
64
- }
65
- }, true);
66
-
67
- })
68
- return promise;
69
- }
70
-
71
- unsubscribe(subscriptionId, callback) {
72
- const URL = this.API_URL + `/${this.project_id}/subscriptions/${subscriptionId}`;
73
- const HTTPREQUEST = {
74
- url: URL,
75
- headers: {
76
- 'Content-Type': 'application/json',
77
- 'Authorization': this.token
78
- },
79
- method: 'DELETE'
80
- };
81
- let promise = new Promise((resolve, reject) => {
82
- TiledeskSubscriptionClient.myrequest(
83
- HTTPREQUEST,
84
- function(err, resbody) {
85
- if (err) {
86
- if (callback) {
87
- callback(err);
88
- }
89
- reject(err);
90
- }
91
- else {
92
- if (callback) {
93
- callback(null, resbody);
94
- }
95
- winston.debug("[TiledeskSubscriptionClient] Unsubscribed");
96
- resolve(resbody);
97
- }
98
- }, true);
99
- })
100
- return promise;
101
- }
102
-
103
-
104
- // HTTP REQUEST
105
-
106
- static async myrequest(options, callback, log) {
107
-
108
- return await axios({
109
- url: options.url,
110
- method: options.method,
111
- data: options.json,
112
- params: options.params,
113
- headers: options.headers
114
- }).then((res) => {
115
- if (res && res.status == 200 && res.data) {
116
- if (callback) {
117
- callback(null, res.data);
118
- }
119
- }
120
- else {
121
- if (callback) {
122
- callback("Response status not 200", null, null);
123
- //callback({ message: "Response status not 200" }, options, res), null, null);
124
- }
125
- }
126
- }).catch((err) => {
127
- if (callback) {
128
- callback(err, null, null);
129
- }
130
- })
131
- }
132
-
133
- }
134
-
135
- module.exports = { TiledeskSubscriptionClient }
@@ -1,55 +0,0 @@
1
- const axios = require("axios").default;
2
- const axiosRetry = require('axios-retry').default;
3
- var winston = require('../winston');
4
-
5
- /*axiosRetry(axios, {
6
- retries: 3,
7
- retryDelay: (...arg) => axiosRetry.exponentialDelay(...arg, 100),
8
- retryCondition(error) {
9
-
10
- if (error.response) {
11
- winston.info("(retryCondition) response status: " + error.response.status);
12
- switch (error.response.status) {
13
- // example: retry only if status is 500 or 501
14
- case 404:
15
- return true;
16
- default:
17
- return false;
18
- }
19
- } else {
20
- winston.info("(retryCondition) no response status. Error message: " + error.message);
21
- return false;
22
- }
23
- },
24
- onRetry: (retryCount, error, requestConfig) => {
25
- winston.info("retry count: " + retryCount);
26
- winston.verbose("retry error: " + error.response.status + " " + error.response.statusText);
27
- }
28
- })
29
- */
30
-
31
- class FileUtils {
32
-
33
-
34
- async downloadFromUrl(url) {
35
-
36
- return new Promise(async (resolve, reject) => {
37
- await axios({
38
- url: url,
39
- responseType: 'arraybuffer',
40
- method: 'GET'
41
- }).then((resbody) => {
42
- const buffer = Buffer.from(resbody.data, 'binary');
43
- resolve(buffer);
44
- //resolve(resbody.data);
45
- }).catch((err) => {
46
- reject(err);
47
- })
48
- })
49
-
50
- }
51
- }
52
-
53
- var fileUtils = new FileUtils();
54
-
55
- module.exports = fileUtils;