wingbot 3.30.2 → 3.31.0

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.0",
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",
@@ -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') {
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
  *
@@ -319,6 +325,17 @@ class ReturnSender {
319
325
  this._visitedInteractions.push(action);
320
326
  }
321
327
 
328
+ /**
329
+ *
330
+ * @param {Buffer} data
331
+ * @param {string} contentType
332
+ * @param {string} fileName
333
+ * @returns {Promise<UploadResult>}
334
+ */
335
+ async upload (data, contentType, fileName) { // eslint-disable-line no-unused-vars
336
+ throw new Error('#upload() not supported by this channel');
337
+ }
338
+
322
339
  send (payload) {
323
340
  if (this._finished) {
324
341
  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([{