wingbot 3.30.2 → 3.31.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.30.2",
3
+ "version": "3.31.2",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -53,6 +53,7 @@
53
53
  "tsd-jsdoc": "^2.5.0"
54
54
  },
55
55
  "dependencies": {
56
+ "form-data": "^4.0.0",
56
57
  "graphql": "^15.8.0",
57
58
  "jsonwebtoken": "^8.5.1",
58
59
  "node-fetch": "^2.6.7",
package/src/BotApp.js CHANGED
@@ -23,6 +23,7 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
23
23
 
24
24
  /** @typedef {import('./CallbackAuditLog')} AuditLog */
25
25
  /** @typedef {import('./BotAppSender').TlsOptions} TlsOptions */
26
+ /** @typedef {import('./ReturnSender').ReturnSenderOptions} ReturnSenderOptions */
26
27
 
27
28
  /**
28
29
  * @typedef {object} BotAppOptions
@@ -35,7 +36,7 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
35
36
  * @prop {AuditLog} [auditLog]
36
37
  * @prop {TlsOptions} [tls]
37
38
  *
38
- * @typedef {ProcessorOptions & BotAppOptions} Options
39
+ * @typedef {ProcessorOptions & BotAppOptions & ReturnSenderOptions} Options
39
40
  */
40
41
 
41
42
  /**
@@ -66,9 +67,20 @@ class BotApp {
66
67
  preferSynchronousResponse = false,
67
68
  auditLog = null,
68
69
  tls = null,
70
+
71
+ textFilter,
72
+ logStandbyEvents,
73
+ confidentInputFilter,
74
+
69
75
  ...processorOptions
70
76
  } = options;
71
77
 
78
+ this._returnSenderOptions = {
79
+ textFilter,
80
+ logStandbyEvents,
81
+ confidentInputFilter
82
+ };
83
+
72
84
  this._secret = Promise.resolve(secret);
73
85
  this._fetch = fetch; // mock
74
86
  this._appId = appId;
@@ -200,7 +212,8 @@ class BotApp {
200
212
  const { mid = null } = message;
201
213
 
202
214
  if (sync || this._preferSynchronousResponse) {
203
- const sender = new ReturnSender({}, senderId, message, this._senderLogger);
215
+ const options = this._returnSenderOptions;
216
+ const sender = new ReturnSender(options, senderId, message, this._senderLogger);
204
217
  sender.propagatesWaitEvent = true;
205
218
  const res = await this._processor.processMessage(message, pageId, sender, { appId });
206
219
  await this._processSenderResponses(sender, senderId, pageId, headers);
@@ -226,6 +239,7 @@ class BotApp {
226
239
  }
227
240
 
228
241
  const options = {
242
+ ...this._returnSenderOptions,
229
243
  apiUrl: this._apiUrl,
230
244
  pageId,
231
245
  appId,
@@ -5,6 +5,7 @@
5
5
 
6
6
  const jwt = require('jsonwebtoken');
7
7
  const { default: fetch, Headers } = require('node-fetch');
8
+ const FormData = require('form-data');
8
9
  const crypto = require('crypto');
9
10
  const { Agent } = require('https');
10
11
  const { promisify } = require('util');
@@ -12,6 +13,7 @@ const ReturnSender = require('./ReturnSender');
12
13
 
13
14
  /** @typedef {import('./ReturnSender').ReturnSenderOptions} ReturnSenderOptions */
14
15
  /** @typedef {import('./ReturnSender').ChatLogStorage} ChatLogStorage */
16
+ /** @typedef {import('./ReturnSender').UploadResult} UploadResult */
15
17
 
16
18
  /**
17
19
  * @typedef {object} TlsOptions
@@ -91,6 +93,38 @@ class BotAppSender extends ReturnSender {
91
93
  return this._agent;
92
94
  }
93
95
 
96
+ /**
97
+ *
98
+ * @param {Buffer} data
99
+ * @param {string} contentType
100
+ * @param {string} fileName
101
+ * @returns {Promise<UploadResult>}
102
+ */
103
+ async upload (data, contentType, fileName) {
104
+ const formData = new FormData();
105
+
106
+ const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36).padEnd(11, '0');
107
+
108
+ formData.append('nonce', nonce);
109
+ formData.append('f0', data, { filename: fileName, contentType });
110
+
111
+ const [token, agent] = await Promise.all([
112
+ BotAppSender.signBody(nonce, this._secret, this._appId),
113
+ this._getAgent()
114
+ ]);
115
+
116
+ const headers = new Headers();
117
+
118
+ headers.set('Authorization', token);
119
+
120
+ const response = await this._fetch(this._apiUrl, {
121
+ headers, body: formData, agent, method: 'POST'
122
+ })
123
+ .then((r) => r.json());
124
+
125
+ return response;
126
+ }
127
+
94
128
  async _send (payload) {
95
129
  // attach sender
96
130
  if (typeof payload.sender === 'undefined') {
@@ -563,7 +563,10 @@ class BuildRouter extends Router {
563
563
 
564
564
  if ([
565
565
  'botbuild.include',
566
- 'botbuild.path'
566
+ 'botbuild.path',
567
+ 'botbuild.customCode',
568
+ 'botbuild.inlineCode',
569
+ 'botbuild.plugin'
567
570
  ].includes(type)) {
568
571
  return fn;
569
572
  }
package/src/Responder.js CHANGED
@@ -60,6 +60,8 @@ Object.freeze(ExpectedInput);
60
60
  * @prop {string} [voice]
61
61
  */
62
62
 
63
+ /** @typedef {import('./ReturnSender').UploadResult} UploadResult */
64
+
63
65
  /**
64
66
  * Instance of responder is passed as second parameter of handler (res)
65
67
  *
@@ -537,6 +539,7 @@ class Responder {
537
539
  });
538
540
  } else {
539
541
  this._quickReplyCollector.push({
542
+ // @ts-ignore
540
543
  action: this.toAbsoluteAction(action),
541
544
  title,
542
545
  data,
@@ -749,6 +752,30 @@ class Responder {
749
752
  return ret;
750
753
  }
751
754
 
755
+ /**
756
+ *
757
+ * @param {Buffer} data
758
+ * @param {string} contentType
759
+ * @param {string} fileName
760
+ * @returns {Promise<UploadResult>}
761
+ */
762
+ async upload (data, contentType, fileName) {
763
+ const result = await this._messageSender.upload(data, contentType, fileName);
764
+
765
+ if (!result.url) {
766
+ throw new Error(`Got no url on file upload ${fileName}. Probably a compatibility issue.`);
767
+ }
768
+
769
+ let [type] = contentType.split('/');
770
+
771
+ if (!['image', 'video', 'audio'].includes(type)) {
772
+ type = 'file';
773
+ }
774
+
775
+ this._attachment(result.url, type, true);
776
+ return result;
777
+ }
778
+
752
779
  /**
753
780
  * Sends image as response. Requires appUrl option to send images from server
754
781
  *
@@ -23,6 +23,12 @@ const { FLAG_DO_NOT_LOG } = require('./flags');
23
23
  * @prop {textFilter} [confidentInputFilter] - filter for confident input (@CONFIDENT)
24
24
  */
25
25
 
26
+ /**
27
+ * @typedef {object} UploadResult
28
+ * @prop {string} [url]
29
+ * @prop {string|number} [attachmentId]
30
+ */
31
+
26
32
  /**
27
33
  * Text filter function
28
34
  *
@@ -190,9 +196,22 @@ class ReturnSender {
190
196
  ? this.confidentInputFilter
191
197
  : this.textFilter;
192
198
 
199
+ let { message } = payload;
200
+
201
+ if (message && message.voice && message.voice.ssml) {
202
+ message = {
203
+ ...message,
204
+ text: message.text ? message.text : message.voice.ssml,
205
+ voice: {
206
+ ...message.voice,
207
+ ssml: filter(message.voice.ssml)
208
+ }
209
+ };
210
+ }
211
+
193
212
  // text message
194
- if (payload.message && payload.message.text) {
195
- let { text } = payload.message;
213
+ if (message && message.text) {
214
+ let { text } = message;
196
215
 
197
216
  if (req && req._anonymizedText) {
198
217
  text = req._anonymizedText;
@@ -201,27 +220,27 @@ class ReturnSender {
201
220
  return {
202
221
  ...payload,
203
222
  message: {
204
- ...payload.message,
223
+ ...message,
205
224
  text: filter(text)
206
225
  }
207
226
  };
208
227
  }
209
228
 
210
229
  // button message
211
- if (payload.message && payload.message.attachment
212
- && payload.message.attachment.type === 'template'
213
- && payload.message.attachment.payload
214
- && payload.message.attachment.payload.text) {
230
+ if (message && message.attachment
231
+ && message.attachment.type === 'template'
232
+ && message.attachment.payload
233
+ && message.attachment.payload.text) {
215
234
 
216
235
  return {
217
236
  ...payload,
218
237
  message: {
219
- ...payload.message,
238
+ ...message,
220
239
  attachment: {
221
- ...payload.message.attachment,
240
+ ...message.attachment,
222
241
  payload: {
223
- ...payload.message.attachment.payload,
224
- text: filter(payload.message.attachment.payload.text)
242
+ ...message.attachment.payload,
243
+ text: filter(message.attachment.payload.text)
225
244
  }
226
245
  }
227
246
  }
@@ -319,6 +338,17 @@ class ReturnSender {
319
338
  this._visitedInteractions.push(action);
320
339
  }
321
340
 
341
+ /**
342
+ *
343
+ * @param {Buffer} data
344
+ * @param {string} contentType
345
+ * @param {string} fileName
346
+ * @returns {Promise<UploadResult>}
347
+ */
348
+ async upload (data, contentType, fileName) { // eslint-disable-line no-unused-vars
349
+ throw new Error('#upload() not supported by this channel');
350
+ }
351
+
322
352
  send (payload) {
323
353
  if (this._finished) {
324
354
  throw new Error('Cannot send message after sender is finished');
@@ -19,14 +19,17 @@ const {
19
19
  FEATURE_SSML, FEATURE_TEXT, FEATURE_VOICE
20
20
  } = require('../features');
21
21
 
22
+ /** @typedef {import('../Responder').VoiceControl} VoiceControl */
23
+ /** @typedef {import('./utils').Translations} Translations */
24
+ /** @typedef {import('./utils').TextObject} TextObject */
22
25
  /**
23
26
  * Returns voice control props from params
24
27
  *
25
28
  * @param {any} params
26
29
  * @param {string} lang
27
- * @returns {null | import('../Responder').VoiceControl}
30
+ * @returns {null | VoiceControl}
28
31
  */
29
- function getVoiceControl (params, lang = null) {
32
+ function getVoiceControlFromParams (params, lang = null) {
30
33
  const voiceControl = {};
31
34
 
32
35
  const voiceControlProps = ['speed', 'pitch', 'volume', 'voice', 'style', 'language'];
@@ -105,10 +108,10 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
105
108
  }
106
109
 
107
110
  /**
108
- * @param {import('./utils').Translations} text
111
+ * @param {Translations} text
109
112
  * @param {string[]} features
110
113
  * @param {string} lang
111
- * @returns {{translations: import('./utils').TextObject[],ssmlAlternatives:string[] | null}}
114
+ * @returns {{translations:TextObject[],ssmlAlternatives:string[] | null}}
112
115
  */
113
116
  function findSupportedMessages (text, features, lang = null) {
114
117
  let translations = getLanguageTextObjects(text, lang);
@@ -184,7 +187,6 @@ function message (params, {
184
187
  const data = stateData(req, res);
185
188
 
186
189
  // filter supported messages
187
- /** @type {{translations:import('./utils').TextObject[],ssmlAlternatives:string[]}} */
188
190
  const supportedText = findSupportedMessages(
189
191
  params.text,
190
192
  req.features,
@@ -235,7 +237,7 @@ function message (params, {
235
237
  }
236
238
 
237
239
  // generate voice control
238
- let voiceControl = getVoiceControl(params, data.lang);
240
+ let voiceControl = getVoiceControlFromParams(params, data.lang);
239
241
  if (supportedText.ssmlAlternatives) {
240
242
  // find SSML alternative
241
243
  const ssmlAlternativeTemplate = randomizedCompiler([{