wingbot 3.31.0 → 3.32.0-alpha.1

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.31.0",
3
+ "version": "3.32.0-alpha.1",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Ai.js CHANGED
@@ -229,7 +229,7 @@ class Ai {
229
229
  *
230
230
  * @param {string} path
231
231
  * @param {IntentRule|IntentRule[]} intents
232
- * @param {string} [title] - disambiguation title
232
+ * @param {string|Function} [title] - disambiguation title
233
233
  * @param {object} [meta] - metadata for multibot environments
234
234
  * @param {object} [meta.targetAppId] - target application id
235
235
  * @param {object} [meta.targetAction] - target action
@@ -276,7 +276,7 @@ class Ai {
276
276
  *
277
277
  * @param {string} path
278
278
  * @param {IntentRule|IntentRule[]} intents
279
- * @param {string} [title] - disambiguation title
279
+ * @param {string|Function} [title] - disambiguation title
280
280
  * @returns {object} - the middleware
281
281
  * @memberOf Ai
282
282
  * @example
@@ -17,6 +17,67 @@ const { shouldExecuteResolver } = require('./resolvers/resolverTags');
17
17
 
18
18
  const MESSAGE_RESOLVER_NAME = 'botbuild.message';
19
19
 
20
+ /** @typedef {import('./Router').Middleware} Middleware */
21
+
22
+ /**
23
+ * @typedef {object} Resolver
24
+ * @prop {string} type
25
+ * @prop {object} params
26
+ * @prop {string} [params.staticBlockId]
27
+ * @prop {string} [tag]
28
+ */
29
+
30
+ /** @typedef {import('./resolvers/bounce').BounceAllow} BounceAllow */
31
+ /** @typedef {import('./resolvers/bounce').BounceReturn} BounceReturn */
32
+
33
+ /**
34
+ * @typedef {object} Route
35
+ * @prop {number} id
36
+ * @prop {string|null} path
37
+ * @prop {Resolver[]} resolvers
38
+ * @prop {boolean} [isFallback]
39
+ * @prop {string[]} [aiTags]
40
+ * @prop {boolean} [isResponder]
41
+ * @prop {number} [respondsToRouteId]
42
+ * @prop {string|any[]} [aiTitle]
43
+ * @prop {boolean} [aiGlobal]
44
+ * @prop {BounceAllow} [bounceAllowedTo]
45
+ * @prop {BounceReturn} [bounceReturn]
46
+ * @prop {boolean} [isEntryPoint]
47
+ */
48
+
49
+ /**
50
+ * @typedef {object} RouteTransformation
51
+ * @prop {string} [expectedPath]
52
+ *
53
+ * @typedef {RouteTransformation & Route} TransformedRoute
54
+ */
55
+
56
+ /**
57
+ * @typedef {Map<string|number,string>} LinksMap
58
+ */
59
+
60
+ /** @type {TransformedRoute} */
61
+ const DUMMY_ROUTE = { id: 0, path: null, resolvers: [] };
62
+
63
+ /**
64
+ * @typedef {object} Block
65
+ * @prop {string} staticBlockId
66
+ * @prop {Route[]} routes
67
+ * @prop {boolean} [isRoot]
68
+ * @prop {boolean} [disabled]
69
+ * @prop {string} [blockName]
70
+ * @prop {string} [blockType]
71
+ */
72
+
73
+ /**
74
+ * @typedef {object} BotConfig
75
+ * @prop {string} botId - the ID of bot
76
+ * @prop {string} snapshot - snapshot stage of bot
77
+ * @prop {string|Promise<string>} token - authorization token for bot
78
+ * @prop {string} [url] - specify alternative configuration resource
79
+ */
80
+
20
81
  /**
21
82
  * @typedef {object} ConfigStorage
22
83
  * @prop {{():Promise}} invalidateConfig
@@ -25,6 +86,61 @@ const MESSAGE_RESOLVER_NAME = 'botbuild.message';
25
86
  * @prop {{():Promise<Object>}} getConfig
26
87
  */
27
88
 
89
+ /**
90
+ * @typedef {object} RouteConfig
91
+ * @prop {string} path
92
+ * @prop {boolean} enabled
93
+ * @prop {object} configuration
94
+ */
95
+
96
+ /**
97
+ * @callback LinkTranslator
98
+ * @param {string} senderId
99
+ * @param {string} textLabel
100
+ * @param {string} urlText
101
+ * @param {boolean} [isExtUrl]
102
+ * @param {object} [state]
103
+ * @returns {string}
104
+ */
105
+
106
+ /**
107
+ * @typedef {object} BotContext
108
+ * @prop {LinkTranslator} [linksTranslator] - function, that translates links globally
109
+ * @prop {ConfigStorage} [configStorage] - function, that translates links globally
110
+ * @prop {boolean} [allowForbiddenSnippetWords] - disable security rule
111
+ * @prop {RouteConfig[]} [routeConfigs] - list of disabled routes
112
+ * @prop {object} [configuration] - context data
113
+ * @prop {LinksMap} [linksMap]
114
+ * @prop {boolean} [isLastIndex]
115
+ * @prop {boolean} [isLastMessage]
116
+ * @prop {BuildRouter} [router]
117
+ * @prop {string} [path]
118
+ * @prop {boolean} [isFallback]
119
+ * @prop {string} [expectedPath]
120
+ * @prop {boolean} [isResponder]
121
+ * @prop {string|number} [routeId]
122
+ * @prop {string} [blockName]
123
+ * @prop {string} [blockType]
124
+ * @prop {boolean} [isRoot]
125
+ * @prop {string} [staticBlockId]
126
+ * @prop {Block[]} [blocks]
127
+ * @prop {object} [BuildRouter]
128
+ */
129
+
130
+ function deepFreeze (object) {
131
+ const propNames = Object.getOwnPropertyNames(object);
132
+
133
+ for (const name of propNames) {
134
+ const value = object[name];
135
+
136
+ if (value && typeof value === 'object') {
137
+ deepFreeze(value);
138
+ }
139
+ }
140
+
141
+ return Object.freeze(object);
142
+ }
143
+
28
144
  /**
29
145
  * Build bot from Wingbot configuration file or snapshot url
30
146
  *
@@ -36,16 +152,15 @@ class BuildRouter extends Router {
36
152
  * Create new router from configuration
37
153
  *
38
154
  * @constructor
39
- * @param {object} block
40
- * @param {string} [block.botId] - the ID of bot
41
- * @param {string} [block.snapshot] - snapshot stage of bot
42
- * @param {string|Promise<string>} [block.token] - authorization token for bot
43
- * @param {string} [block.url] - specify alternative configuration resource
155
+ * @param {BotConfig|Block} block
44
156
  * @param {Plugins} plugins - custom code blocks resource
45
157
  * @param {object} context - the building context
46
158
  * @param {object} [context.linksTranslator] - function, that translates links globally
47
159
  * @param {ConfigStorage} [context.configStorage] - function, that translates links globally
48
160
  * @param {boolean} [context.allowForbiddenSnippetWords] - disable security rule
161
+ * @param {RouteConfig[]} [context.routeConfigs] - list of disabled routes
162
+ * @param {object} [context.config] - context data
163
+ * @param {object} [context.configuration]
49
164
  * @param {fetch} [fetchFn] - override a request function
50
165
  * @example
51
166
  *
@@ -76,8 +191,10 @@ class BuildRouter extends Router {
76
191
 
77
192
  this._plugins = plugins;
78
193
 
194
+ /** @type {BotContext} */
79
195
  this._context = context;
80
196
 
197
+ /** @type {LinksMap} */
81
198
  this._linksMap = new Map();
82
199
 
83
200
  this._loadBotUrl = null;
@@ -94,7 +211,7 @@ class BuildRouter extends Router {
94
211
 
95
212
  this.resources = defaultResourceMap();
96
213
 
97
- this._loadBotAuthorization = block.token || null;
214
+ this._loadBotAuthorization = 'token' in block ? block.token : null;
98
215
 
99
216
  this._configStorage = context.configStorage;
100
217
 
@@ -112,7 +229,7 @@ class BuildRouter extends Router {
112
229
  this._snapshot = null;
113
230
  this._botId = null;
114
231
 
115
- if (typeof block.routes === 'object') {
232
+ if ('routes' in block) {
116
233
  this._buildBot(block);
117
234
  } else if (typeof block.url === 'string') {
118
235
  this._loadBotUrl = block.url;
@@ -314,6 +431,12 @@ class BuildRouter extends Router {
314
431
  }
315
432
  }
316
433
 
434
+ /**
435
+ *
436
+ * @param {Block} block
437
+ * @param {number} setConfigTimestamp
438
+ * @param {string} lastmod
439
+ */
317
440
  _buildBot (block, setConfigTimestamp = Number.MAX_SAFE_INTEGER, lastmod = '-') {
318
441
  try {
319
442
  if (setConfigTimestamp !== Number.MAX_SAFE_INTEGER) {
@@ -388,7 +511,13 @@ class BuildRouter extends Router {
388
511
  });
389
512
  }
390
513
 
514
+ /**
515
+ *
516
+ * @param {Block} block
517
+ * @returns {LinksMap}
518
+ */
391
519
  _createLinksMap (block) {
520
+ /** @type {LinksMap} */
392
521
  const linksMap = new Map();
393
522
 
394
523
  block.routes
@@ -400,35 +529,140 @@ class BuildRouter extends Router {
400
529
  if (prevLinksMap) {
401
530
  for (const [from, to] of prevLinksMap.entries()) {
402
531
  if (!linksMap.has(from)) {
403
-
404
- (path.posix || path).join('..', to);
405
-
406
- linksMap.set(from, (path.posix || path).join('..', to));
532
+ linksMap.set(from, this._joinPaths('..', to));
407
533
  }
408
534
  }
409
535
  }
410
536
 
411
537
  block.routes.forEach((route) => {
412
- let resolver;
413
- for (resolver of route.resolvers) {
414
- if (resolver.type !== 'botbuild.include') {
415
- continue;
416
- }
417
- this._findEntryPointsInResolver(linksMap, resolver, route, this._context);
538
+ const enabledNestedBlock = this._getBlockById(this._getIncludedBlockId(route));
539
+ if (!enabledNestedBlock) {
540
+ return;
541
+ }
542
+ const routeConfig = this._getRouteConfig(route);
543
+ if (this._enabledByRouteConfig(routeConfig)) {
544
+ this._findEntryPointsInResolver(linksMap, enabledNestedBlock, route);
418
545
  }
419
546
  });
420
547
 
421
548
  return linksMap;
422
549
  }
423
550
 
424
- _findEntryPointsInResolver (linksMap, resolver, route, context) {
425
- const includedBlock = (context.blocks || [])
426
- .find((b) => b.staticBlockId === resolver.params.staticBlockId);
551
+ /**
552
+ *
553
+ * @param {RouteConfig} routeConfig
554
+ */
555
+ _enabledByRouteConfig (routeConfig) {
556
+ return !this._context.routeConfigs || (routeConfig && routeConfig.enabled);
557
+ }
427
558
 
428
- if (!includedBlock) {
429
- return;
559
+ _joinPaths (...args) {
560
+ return (path.posix || path).join(...args);
561
+ }
562
+
563
+ _normalizeConfigPath (routePath, ctxPath = null) {
564
+ const joined = ctxPath
565
+ ? this._joinPaths(ctxPath, routePath)
566
+ : routePath;
567
+
568
+ return joined
569
+ .replace(/^\/?|(_responder)?\/?$/g, '/');
570
+ }
571
+
572
+ /**
573
+ *
574
+ * @param {Route} route
575
+ * @returns {string|null}
576
+ */
577
+ _getIncludedBlockId (route) {
578
+ const includeResolver = route.resolvers.find((r) => r.type === 'botbuild.include');
579
+
580
+ return includeResolver
581
+ ? includeResolver.params.staticBlockId
582
+ : null;
583
+ }
584
+
585
+ /**
586
+ *
587
+ * @param {string} staticBlockId
588
+ * @returns {Block|null}
589
+ */
590
+ _getBlockById (staticBlockId) {
591
+ if (!staticBlockId) {
592
+ return null;
593
+ }
594
+ const nestedBlock = (this._context.blocks || [])
595
+ .find((b) => b.staticBlockId === staticBlockId);
596
+
597
+ if (!nestedBlock || nestedBlock.disabled) {
598
+ return null;
599
+ }
600
+ return nestedBlock;
601
+ }
602
+
603
+ /**
604
+ *
605
+ * @param {TransformedRoute} route
606
+ * @returns {RouteConfig}
607
+ */
608
+ _getRouteConfig (route) {
609
+ const { path: ctxPath, routeConfigs } = this._context;
610
+ if (!routeConfigs || !route.path || route.isFallback) {
611
+ return null;
612
+ }
613
+
614
+ let rPath;
615
+ if (!route.isResponder) {
616
+ rPath = route.path;
617
+ } else if (route.expectedPath) {
618
+ rPath = route.expectedPath;
619
+ } else if (this._linksMap.has(route.respondsToRouteId)) {
620
+ rPath = this._linksMap.get(route.respondsToRouteId);
621
+ } else {
622
+ throw new Error('Illegal state');
430
623
  }
431
624
 
625
+ const routePath = this._normalizeConfigPath(rPath, ctxPath);
626
+ return routeConfigs.find((config) => {
627
+ const configPath = this._normalizeConfigPath(config.path);
628
+
629
+ return configPath === routePath;
630
+ });
631
+ }
632
+
633
+ /**
634
+ *
635
+ * @param {string} routePath
636
+ * @returns {object}
637
+ */
638
+ getConfiguration (routePath) {
639
+ const { path: ctxPath, routeConfigs, configuration = {} } = this._context;
640
+ if (!routeConfigs || !routePath) {
641
+ return deepFreeze(configuration);
642
+ }
643
+ const normalized = this._normalizeConfigPath(routePath, ctxPath);
644
+
645
+ const configs = routeConfigs
646
+ .filter((config) => {
647
+ const configPath = this._normalizeConfigPath(config.path);
648
+ return normalized.startsWith(configPath);
649
+ })
650
+ .sort((a, z) => a.path.length - z.path.length);
651
+
652
+ return deepFreeze(configs.reduce((cfg, config) => ({
653
+ ...cfg,
654
+ ...config.configuration
655
+ }), configuration));
656
+ }
657
+
658
+ /**
659
+ *
660
+ * @param {LinksMap} linksMap
661
+ * @param {Block} includedBlock
662
+ * @param {TransformedRoute} route
663
+ * @returns {void}
664
+ */
665
+ _findEntryPointsInResolver (linksMap, includedBlock, route) {
432
666
  let basePath = `${route.path}/`;
433
667
 
434
668
  if (route.isFallback) {
@@ -436,14 +670,20 @@ class BuildRouter extends Router {
436
670
  }
437
671
 
438
672
  includedBlock.routes.forEach((blockRoute) => {
439
- if (!blockRoute.isEntryPoint || blockRoute.isRoot) {
673
+ if (!blockRoute.isEntryPoint) {
440
674
  return;
441
675
  }
442
676
 
443
- linksMap.set(`${blockRoute.id}`, `${basePath}${blockRoute.path}`);
677
+ linksMap.set(blockRoute.id, `${basePath}${blockRoute.path}`);
444
678
  });
445
679
  }
446
680
 
681
+ /**
682
+ *
683
+ * @param {TransformedRoute} route
684
+ * @param {boolean} nextRouteIsSameResponder
685
+ * @returns {Middleware[]}
686
+ */
447
687
  _buildRouteHead (route, nextRouteIsSameResponder) {
448
688
  const resolvers = [];
449
689
 
@@ -451,10 +691,10 @@ class BuildRouter extends Router {
451
691
  let aiResolver = null;
452
692
 
453
693
  if (route.aiTags && route.aiTags.length) {
454
- let { aiTitle = null } = route;
694
+ let aiTitle = null;
455
695
 
456
- if (aiTitle) {
457
- aiTitle = cachedTranslatedCompilator(aiTitle);
696
+ if (route.aiTitle) {
697
+ aiTitle = cachedTranslatedCompilator(route.aiTitle);
458
698
  }
459
699
 
460
700
  if (route.aiGlobal) {
@@ -486,8 +726,25 @@ class BuildRouter extends Router {
486
726
  return resolvers;
487
727
  }
488
728
 
729
+ /**
730
+ *
731
+ * @param {TransformedRoute[]} routes
732
+ */
489
733
  _buildRoutes (routes) {
490
734
  routes.forEach((route, i) => {
735
+ const routeConfig = this._getRouteConfig(route);
736
+
737
+ if (routeConfig && !routeConfig.enabled) {
738
+ return;
739
+ }
740
+
741
+ const includedBlockId = this._getIncludedBlockId(route);
742
+ const nestedBlock = this._getBlockById(includedBlockId);
743
+
744
+ if (includedBlockId && (!nestedBlock || !this._enabledByRouteConfig(routeConfig))) {
745
+ return;
746
+ }
747
+
491
748
  const nextRoute = routes.length > (i + 1)
492
749
  ? routes[i + 1]
493
750
  : null;
@@ -515,6 +772,11 @@ class BuildRouter extends Router {
515
772
  });
516
773
  }
517
774
 
775
+ /**
776
+ *
777
+ * @param {Resolver[]} resolvers
778
+ * @returns {number}
779
+ */
518
780
  _lastMessageIndex (resolvers) {
519
781
  for (let i = resolvers.length - 1; i >= 0; i--) {
520
782
  if (resolvers[i].type === MESSAGE_RESOLVER_NAME) {
@@ -524,15 +786,30 @@ class BuildRouter extends Router {
524
786
  return -1;
525
787
  }
526
788
 
527
- buildResolvers (resolvers, route = {}, expectedToAddResolver = false) {
789
+ /**
790
+ *
791
+ * @param {Resolver[]} resolvers
792
+ * @param {TransformedRoute} route
793
+ * @param {*} expectedToAddResolver
794
+ * @returns {Middleware[]}
795
+ */
796
+ buildResolvers (resolvers, route = DUMMY_ROUTE, expectedToAddResolver = false) {
528
797
  const {
529
798
  path: ctxPath, isFallback, isResponder, expectedPath, id
530
799
  } = route;
531
800
 
801
+ const routeConfig = this._getRouteConfig(route);
802
+ const routeConfigData = routeConfig && routeConfig.configuration;
803
+
532
804
  const lastMessageIndex = this._lastMessageIndex(resolvers);
533
805
  const lastIndex = resolvers.length - 1;
534
806
 
535
807
  return resolvers.map((resolver, i) => {
808
+ const configuration = deepFreeze({
809
+ ...this._context.configuration,
810
+ ...routeConfigData
811
+ });
812
+
536
813
  const context = {
537
814
  ...this._context,
538
815
  isLastIndex: lastIndex === i && !expectedToAddResolver,
@@ -543,13 +820,23 @@ class BuildRouter extends Router {
543
820
  isFallback,
544
821
  isResponder,
545
822
  expectedPath,
546
- routeId: id
823
+ routeId: id,
824
+ configuration
547
825
  };
548
826
 
549
- return this._resolverFactory(resolver, context);
827
+ const resFn = this._resolverFactory(resolver, context);
828
+ // @ts-ignore
829
+ resFn.configuration = configuration;
830
+ return resFn;
550
831
  });
551
832
  }
552
833
 
834
+ /**
835
+ *
836
+ * @param {Resolver} resolver
837
+ * @param {BotContext} context
838
+ * @returns {Middleware}
839
+ */
553
840
  _resolverFactory (resolver, context) {
554
841
  const { type } = resolver;
555
842
 
@@ -602,9 +889,9 @@ class BuildRouter extends Router {
602
889
  }
603
890
 
604
891
  /**
605
- * @param {object[]} blocks - blocks list
892
+ * @param {Block[]} blocks - blocks list
606
893
  * @param {Plugins} [plugins]
607
- * @param {object} [context]
894
+ * @param {BotContext} [context]
608
895
  */
609
896
  BuildRouter.fromData = function fromData (blocks, plugins = new Plugins(), context = {}) {
610
897
 
package/src/Processor.js CHANGED
@@ -14,6 +14,7 @@ const { mergeState } = require('./utils/stateVariables');
14
14
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').Intent} Intent */
15
15
  /** @typedef {import('./ReducerWrapper')} ReducerWrapper */
16
16
  /** @typedef {import('./Router')} Router */
17
+ /** @typedef {import('./BuildRouter')} BuildRouter */
17
18
 
18
19
  /**
19
20
  * @typedef {object} AutoTypingConfig
@@ -121,7 +122,7 @@ class Processor extends EventEmitter {
121
122
  /**
122
123
  * Creates an instance of Processor
123
124
  *
124
- * @param {ReducerWrapper|Router} reducer
125
+ * @param {ReducerWrapper|Router|BuildRouter} reducer
125
126
  * @param {ProcessorOptions} [options] - processor options
126
127
  *
127
128
  * @memberOf Processor
@@ -548,6 +549,11 @@ class Processor extends EventEmitter {
548
549
  res.setBookmark(aByAi);
549
550
  }
550
551
 
552
+ if ('getConfiguration' in this.reducer) {
553
+ const configuration = this.reducer.getConfiguration(state._lastAction);
554
+ req.configuration = Object.freeze(configuration);
555
+ }
556
+
551
557
  // process setState
552
558
  const setState = req.getSetState(req.AI_SETSTATE.EXCLUDE_WITH_SET_ENTITIES);
553
559
  await Ai.ai.processSetStateEntities(req, setState);
package/src/Request.js CHANGED
@@ -258,6 +258,9 @@ class Request {
258
258
  * @constant {string} FEATURE_TEXT channel supports text communication
259
259
  */
260
260
  this.FEATURE_TEXT = FEATURE_TEXT;
261
+
262
+ /** @type {object} */
263
+ this.configuration = Object.freeze({});
261
264
  }
262
265
 
263
266
  get data () {
package/src/Router.js CHANGED
@@ -140,7 +140,12 @@ class Router extends ReducerWrapper {
140
140
 
141
141
  const reducersArray = reducer.map((re) => {
142
142
  const {
143
- resolverPath, reduce, isReducer, globalIntents: gis, globalIntentsMeta = {}
143
+ resolverPath,
144
+ reduce,
145
+ isReducer,
146
+ globalIntents: gis,
147
+ globalIntentsMeta,
148
+ configuration
144
149
  } = this._createReducer(
145
150
  re,
146
151
  pathContext.path
@@ -149,16 +154,19 @@ class Router extends ReducerWrapper {
149
154
  Object.assign(pathContext, { path: resolverPath });
150
155
  Object.assign(pathContext.globalIntentsMeta, globalIntentsMeta);
151
156
  isAnyReducer = isAnyReducer || isReducer;
152
- return { reduce, isReducer };
157
+ return { reduce, isReducer, configuration };
153
158
  });
154
159
 
155
160
  return {
156
- reducers: reducersArray, isReducer: isAnyReducer, isOr: true, globalIntents
161
+ reducers: reducersArray,
162
+ isReducer: isAnyReducer,
163
+ isOr: true,
164
+ globalIntents
157
165
  };
158
166
  }
159
167
 
160
168
  const {
161
- resolverPath, reduce, isReducer, globalIntents, globalIntentsMeta = {}
169
+ resolverPath, reduce, isReducer, globalIntents, globalIntentsMeta, configuration
162
170
  } = this._createReducer(
163
171
  reducer,
164
172
  pathContext.path
@@ -167,7 +175,9 @@ class Router extends ReducerWrapper {
167
175
  Object.assign(pathContext, { path: resolverPath });
168
176
  Object.assign(pathContext.globalIntentsMeta, globalIntentsMeta);
169
177
 
170
- return { reduce, isReducer, globalIntents };
178
+ return {
179
+ reduce, isReducer, globalIntents, configuration
180
+ };
171
181
  });
172
182
  }
173
183
 
@@ -178,7 +188,8 @@ class Router extends ReducerWrapper {
178
188
  const {
179
189
  globalIntents = new Map(),
180
190
  path,
181
- globalIntentsMeta = {}
191
+ globalIntentsMeta = {},
192
+ configuration = null
182
193
  } = reducer;
183
194
 
184
195
  if (typeof reducer === 'string' || path) {
@@ -219,7 +230,7 @@ class Router extends ReducerWrapper {
219
230
  }
220
231
 
221
232
  return {
222
- resolverPath, isReducer, reduce, globalIntents, globalIntentsMeta
233
+ resolverPath, isReducer, reduce, globalIntents, globalIntentsMeta, configuration
223
234
  };
224
235
  }
225
236
 
@@ -291,6 +302,9 @@ class Router extends ReducerWrapper {
291
302
 
292
303
  let pathContext = `${path === '/' ? '' : path}${route.path.replace(/\/\*/, '')}`;
293
304
  res.setPath(path, route.path);
305
+ if (reducer.configuration) {
306
+ req.configuration = reducer.configuration;
307
+ }
294
308
 
295
309
  let result;
296
310
 
@@ -545,7 +545,7 @@ class Notifications extends EventEmitter {
545
545
  if (!campaign.hasEditableCondition) {
546
546
  fn = customFn(campaign.condition, `Campaign "${campaign.name}" condition`);
547
547
  } else {
548
- fn = customCondition(campaign.editableCondition, `Campaign "${campaign.name}" condition`);
548
+ fn = customCondition(campaign.editableCondition, req.configuration, `Campaign "${campaign.name}" condition`);
549
549
  }
550
550
 
551
551
  const fnRes = fn(req, res);
@@ -22,12 +22,15 @@ const BOUNCE_RETURN = {
22
22
  IF_POSSIBLE: 'ifpos'
23
23
  };
24
24
 
25
+ /** @typedef {import('../BuildRouter').Route} Route */
26
+
27
+ /** @typedef {BOUNCE_RETURN} BounceReturn */
28
+ /** @typedef {BOUNCE_ALLOW} BounceAllow */
29
+
25
30
  /**
26
31
  *
27
32
  *
28
- * @param {object} route
29
- * @param {BOUNCE_ALLOW} [route.bounceAllowedTo]
30
- * @param {BOUNCE_RETURN} [route.bounceReturn]
33
+ * @param {Route} route
31
34
  * @param {boolean} nextRouteIsSameResponder
32
35
  * @param {string} [referredRoutePath]
33
36
  * @returns {Function|null}
@@ -11,19 +11,26 @@ const {
11
11
  processButtons
12
12
  } = require('./utils');
13
13
 
14
- function button (params, {
15
- isLastIndex,
16
- linksMap,
17
- linksTranslator,
18
- allowForbiddenSnippetWords
19
- }) {
14
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
15
+ /** @typedef {import('../Router').Resolver} Resolver */
16
+
17
+ /**
18
+ *
19
+ * @param {object} params
20
+ * @param {BotContext} context
21
+ * @returns {Resolver}
22
+ */
23
+ function button (params, context) {
24
+ const {
25
+ isLastIndex
26
+ } = context;
20
27
  const {
21
28
  buttons = [],
22
29
  text = null
23
30
  } = params;
24
31
  const compiledText = cachedTranslatedCompilator(text);
25
32
 
26
- const condition = getCondition(params, '', allowForbiddenSnippetWords);
33
+ const condition = getCondition(params, context, 'button');
27
34
 
28
35
  const ret = isLastIndex ? Router.END : Router.CONTINUE;
29
36
 
@@ -37,17 +44,15 @@ function button (params, {
37
44
  }
38
45
  }
39
46
 
40
- const state = stateData(req, res);
47
+ const state = stateData(req, res, context.configuration);
41
48
  const tpl = res.button(compiledText(state));
42
49
 
43
50
  processButtons(
44
51
  buttons,
45
52
  state,
46
53
  tpl,
47
- linksMap,
48
54
  req.senderId,
49
- linksTranslator,
50
- allowForbiddenSnippetWords,
55
+ context,
51
56
  req,
52
57
  res
53
58
  );
@@ -17,12 +17,12 @@ const {
17
17
  TYPE_URL_WITH_EXT
18
18
  } = require('./utils');
19
19
 
20
- function carousel (params, {
21
- isLastIndex,
22
- linksMap,
23
- linksTranslator = (a, b, c) => c,
24
- allowForbiddenSnippetWords
25
- }) {
20
+ function carousel (params, context) {
21
+ const {
22
+ isLastIndex,
23
+ linksMap,
24
+ linksTranslator = (a, b, c) => c
25
+ } = context;
26
26
  const {
27
27
  items = [],
28
28
  shareable = false,
@@ -36,7 +36,7 @@ function carousel (params, {
36
36
  return ret;
37
37
  }
38
38
 
39
- const state = stateData(req, res);
39
+ const state = stateData(req, res, context.configuration);
40
40
  const isSquare = imageAspect === ASPECT_SQUARE;
41
41
  const tpl = res.genericTemplate(shareable, isSquare);
42
42
 
@@ -99,10 +99,8 @@ function carousel (params, {
99
99
  buttons,
100
100
  state,
101
101
  elem,
102
- linksMap,
103
102
  senderId,
104
- linksTranslator,
105
- allowForbiddenSnippetWords,
103
+ context,
106
104
  req,
107
105
  res
108
106
  );
@@ -7,11 +7,17 @@ const Router = require('../Router');
7
7
  const getCondition = require('../utils/getCondition');
8
8
  const { stateData, cachedTranslatedCompilator } = require('./utils');
9
9
 
10
- function media (
11
- params,
12
- // @ts-ignore
13
- { isLastIndex } = {}
14
- ) {
10
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
11
+ /** @typedef {import('../Router').Resolver} Resolver */
12
+
13
+ /**
14
+ *
15
+ * @param {object} params
16
+ * @param {BotContext} context
17
+ * @returns {Resolver}
18
+ */
19
+ function media (params, context = {}) {
20
+ const { isLastIndex } = context;
15
21
  const { type, url } = params;
16
22
 
17
23
  const urlString = url || '';
@@ -24,14 +30,14 @@ function media (
24
30
  throw new Error(`Unsupported media type: ${type}`);
25
31
  }
26
32
 
27
- const condition = getCondition(params, 'Media condition');
33
+ const condition = getCondition(params, context, 'Media condition');
28
34
 
29
35
  return (req, res) => {
30
36
  if (condition && !condition(req, res)) {
31
37
  return ret;
32
38
  }
33
39
 
34
- const data = stateData(req, res);
40
+ const data = stateData(req, res, context.configuration);
35
41
  const sendUrl = urlTemplate(data);
36
42
 
37
43
  res[type](sendUrl, true);
@@ -44,10 +44,10 @@ function getVoiceControlFromParams (params, lang = null) {
44
44
  return Object.keys(voiceControl).length > 0 ? voiceControl : null;
45
45
  }
46
46
 
47
- function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
47
+ function parseReplies (replies, linksMap, context) {
48
48
  return replies.map((reply) => {
49
49
 
50
- const condition = getCondition(reply, 'Quick reply condition', allowForbiddenSnippetWords);
50
+ const condition = getCondition(reply, context, 'Quick reply condition');
51
51
 
52
52
  if (reply.isLocation) {
53
53
  return {
@@ -78,6 +78,10 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
78
78
  }
79
79
  }
80
80
 
81
+ if (!action) {
82
+ return null;
83
+ }
84
+
81
85
  const ret = {
82
86
  action,
83
87
  condition,
@@ -104,7 +108,8 @@ function parseReplies (replies, linksMap, allowForbiddenSnippetWords) {
104
108
  }
105
109
 
106
110
  return ret;
107
- });
111
+ })
112
+ .filter((r) => r !== null);
108
113
  }
109
114
 
110
115
  /**
@@ -154,11 +159,20 @@ function findSupportedMessages (text, features, lang = null) {
154
159
  };
155
160
  }
156
161
 
157
- function message (params, {
158
- // @ts-ignore
159
- isLastIndex, isLastMessage, linksMap, allowForbiddenSnippetWords
160
- } = {}) {
162
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
163
+ /** @typedef {import('../Router').Resolver} Resolver */
161
164
 
165
+ /**
166
+ *
167
+ * @param {object} params
168
+ * @param {BotContext} context
169
+ * @returns {Resolver}
170
+ */
171
+ function message (params, context = {}) {
172
+ const {
173
+ // @ts-ignore
174
+ isLastIndex, isLastMessage, linksMap, configuration
175
+ } = context;
162
176
  if (typeof params.text !== 'string' && !Array.isArray(params.text)) {
163
177
  throw new Error('Message should be a text!');
164
178
  }
@@ -168,11 +182,11 @@ function message (params, {
168
182
  if (params.replies && !Array.isArray(params.replies)) {
169
183
  throw new Error('Replies should be an array');
170
184
  } else if (params.replies && params.replies.length > 0) {
171
- quickReplies = parseReplies(params.replies, linksMap, allowForbiddenSnippetWords);
185
+ quickReplies = parseReplies(params.replies, linksMap, context);
172
186
  }
173
187
 
174
188
  // compile condition
175
- const condition = getCondition(params, 'Message condition', allowForbiddenSnippetWords);
189
+ const condition = getCondition(params, context, 'Message condition');
176
190
 
177
191
  const ret = isLastIndex ? Router.END : Router.CONTINUE;
178
192
 
@@ -184,7 +198,7 @@ function message (params, {
184
198
  if (condition && !condition(req, res)) {
185
199
  return ret;
186
200
  }
187
- const data = stateData(req, res);
201
+ const data = stateData(req, res, configuration);
188
202
 
189
203
  // filter supported messages
190
204
  const supportedText = findSupportedMessages(
@@ -7,7 +7,17 @@ const Router = require('../Router');
7
7
  const getCondition = require('../utils/getCondition');
8
8
  const { shouldExecuteResolver } = require('./resolverTags');
9
9
 
10
- function postback (params, { linksMap, isLastIndex, allowForbiddenSnippetWords }) {
10
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
11
+ /** @typedef {import('../Router').Resolver} Resolver */
12
+
13
+ /**
14
+ *
15
+ * @param {object} params
16
+ * @param {BotContext} context
17
+ * @returns {Resolver}
18
+ */
19
+ function postback (params, context) {
20
+ const { linksMap, isLastIndex } = context;
11
21
  const {
12
22
  routeId,
13
23
  postBack: staticAction
@@ -22,12 +32,12 @@ function postback (params, { linksMap, isLastIndex, allowForbiddenSnippetWords }
22
32
  }
23
33
  }
24
34
 
25
- const condition = getCondition(params, '', allowForbiddenSnippetWords);
35
+ const condition = getCondition(params, context, 'postback');
26
36
 
27
37
  const ret = isLastIndex ? Router.END : Router.CONTINUE;
28
38
 
29
39
  return (req, res, postBack) => {
30
- if (!shouldExecuteResolver(req, params)) {
40
+ if (!action || !shouldExecuteResolver(req, params)) {
31
41
  return ret;
32
42
  }
33
43
 
@@ -8,11 +8,20 @@ const Ai = require('../Ai');
8
8
  const { getSetState } = require('../utils/getUpdate');
9
9
  const getCondition = require('../utils/getCondition');
10
10
 
11
- function setState (params, { isLastIndex, allowForbiddenSnippetWords }) {
11
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
12
+ /** @typedef {import('../Router').Resolver} Resolver */
13
+
14
+ /**
15
+ *
16
+ * @param {object} params
17
+ * @param {BotContext} context
18
+ * @returns {Resolver}
19
+ */
20
+ function setState (params, context) {
12
21
 
13
- const condition = getCondition(params, '', allowForbiddenSnippetWords);
22
+ const condition = getCondition(params, context, 'setState');
14
23
 
15
- const ret = isLastIndex ? Router.END : Router.CONTINUE;
24
+ const ret = context.isLastIndex ? Router.END : Router.CONTINUE;
16
25
 
17
26
  return async (req, res) => {
18
27
  if (condition !== null) {
@@ -6,15 +6,24 @@
6
6
  const Router = require('../Router');
7
7
  const getCondition = require('../utils/getCondition');
8
8
 
9
- function subscribtions (params, { isLastIndex, allowForbiddenSnippetWords }) {
9
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
10
+ /** @typedef {import('../Router').Resolver} Resolver */
11
+
12
+ /**
13
+ *
14
+ * @param {object} params
15
+ * @param {BotContext} context
16
+ * @returns {Resolver}
17
+ */
18
+ function subscribtions (params, context) {
10
19
  const {
11
20
  tags = [],
12
21
  unsetTag = false
13
22
  } = params;
14
23
 
15
- const condition = getCondition(params, '', allowForbiddenSnippetWords);
24
+ const condition = getCondition(params, context, 'subscribtions');
16
25
 
17
- const ret = isLastIndex ? Router.END : Router.CONTINUE;
26
+ const ret = context.isLastIndex ? Router.END : Router.CONTINUE;
18
27
  const method = unsetTag ? 'unsubscribe' : 'subscribe';
19
28
 
20
29
  return async (req, res) => {
@@ -35,6 +44,7 @@ function subscribtions (params, { isLastIndex, allowForbiddenSnippetWords }) {
35
44
  }
36
45
 
37
46
  if (tags.length === 0 && unsetTag) {
47
+ // @ts-ignore
38
48
  res.unsubscribe();
39
49
  }
40
50
 
@@ -205,18 +205,18 @@ function getText (text, state) {
205
205
  // eslint-disable-next-line no-unused-vars
206
206
  const DEFAULT_LINK_TRANSLATOR = (senderId, defaultText, urlText, isExtUrl, reqState) => urlText;
207
207
 
208
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
209
+
208
210
  function processButtons (
209
211
  buttons,
210
212
  state,
211
213
  elem,
212
- linksMap,
213
214
  senderId,
214
- linksTranslator,
215
- allowForbiddenSnippetWords,
215
+ context,
216
216
  req,
217
217
  res
218
218
  ) {
219
- const translateLinks = linksTranslator || DEFAULT_LINK_TRANSLATOR;
219
+ const translateLinks = context.linksTranslator || DEFAULT_LINK_TRANSLATOR;
220
220
 
221
221
  buttons.forEach(({
222
222
  title: btnTitle,
@@ -230,7 +230,7 @@ function processButtons (
230
230
  if (hasCondition) {
231
231
  const condition = getCondition({
232
232
  hasCondition, conditionFn, hasEditableCondition, editableCondition
233
- }, 'Quick reply condition', allowForbiddenSnippetWords);
233
+ }, context, 'Quick reply condition');
234
234
 
235
235
  if (!condition(req, res)) {
236
236
  return;
@@ -259,7 +259,7 @@ function processButtons (
259
259
  break;
260
260
  }
261
261
  case TYPE_POSTBACK: {
262
- let postbackAction = linksMap.get(targetRouteId) || action;
262
+ let postbackAction = context.linksMap.get(targetRouteId) || action;
263
263
 
264
264
  if (postbackAction === '/') {
265
265
  postbackAction = './';
@@ -148,15 +148,16 @@ const compare = (variable, operator, value = undefined) => {
148
148
  /**
149
149
  *
150
150
  * @param {{value:string, operator:string, variable:string}[][]} condition
151
+ * @param {object} configuration
151
152
  * @param {string} description
152
153
  */
153
- function customCondition (condition, description = '') {
154
+ function customCondition (condition, configuration, description = '') {
154
155
  if (typeof condition !== 'object' || !Array.isArray(condition)) {
155
156
  throw new Error(`Invalid condition (${description}) type`);
156
157
  }
157
158
 
158
159
  const resolver = (req, res) => condition.some((condList) => condList.every((cond) => {
159
- const data = stateData(req, res);
160
+ const data = stateData(req, res, configuration);
160
161
  const variableValue = getValue(cond.variable, data);
161
162
  const isRegExp = [
162
163
  ConditionOperators['matches regexp'],
@@ -6,7 +6,20 @@
6
6
  const customCondition = require('./customCondition');
7
7
  const customFn = require('./customFn');
8
8
 
9
- module.exports = (params, description = '', allowForbiddenSnippetWords = false) => {
9
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
10
+
11
+ /**
12
+ *
13
+ * @param {object} params
14
+ * @param {BotContext} context
15
+ * @param {string} description
16
+ * @returns {Function}
17
+ */
18
+ module.exports = function getCondition (params, context, description = '') {
19
+ const {
20
+ allowForbiddenSnippetWords = false,
21
+ configuration
22
+ } = context;
10
23
  const {
11
24
  hasCondition = false,
12
25
  conditionFn = '() => true',
@@ -18,7 +31,7 @@ module.exports = (params, description = '', allowForbiddenSnippetWords = false)
18
31
 
19
32
  if (hasCondition) {
20
33
  if (hasEditableCondition) {
21
- condition = customCondition(editableCondition);
34
+ condition = customCondition(editableCondition, configuration, description);
22
35
  } else {
23
36
  condition = customFn(conditionFn, description, allowForbiddenSnippetWords);
24
37
  }
@@ -88,7 +88,7 @@ function toArray (previousValue) {
88
88
  const ENTITY_HBS_REGEXP = /^\s*\{\{\[?@([^@[\]{}\s]+)(\])?\}\}\s*$/;
89
89
  const VARIABLE_HBS_REGEXP = /^\s*\{\{\[?([^@[\]{}\s]+)\]?\}\}\s*$/;
90
90
 
91
- function getSetState (setState, req, res = null, useState = null) {
91
+ function getSetState (setState, req, res = null, useState = null, configuration = null) {
92
92
  if (!setState) {
93
93
  return {};
94
94
  }
@@ -179,7 +179,7 @@ function getSetState (setState, req, res = null, useState = null) {
179
179
  values = [];
180
180
  } else {
181
181
  const useValue = typeof value === 'string'
182
- ? handlebars.compile(value)(stateData(req, res))
182
+ ? handlebars.compile(value)(stateData(req, res, configuration))
183
183
  .split(/(?<!\\),/g)
184
184
  .map((v) => v.replace(/\\,/g, ',').trim())
185
185
  : value;
@@ -205,7 +205,7 @@ function getSetState (setState, req, res = null, useState = null) {
205
205
  set = val;
206
206
  }
207
207
  } else if (typeof val === 'string') {
208
- set = handlebars.compile(val)(stateData(req, res));
208
+ set = handlebars.compile(val)(stateData(req, res, configuration));
209
209
  } else if (val === null
210
210
  || SCALAR_TYPES.includes(typeof val)) {
211
211
  set = val;
@@ -3,11 +3,14 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
- module.exports = function stateData (req, res = null) {
6
+ module.exports = function stateData (req, res = null, configuration = null) {
7
+ const c = configuration || req.configuration;
7
8
  return {
8
9
  ...req.state,
9
10
  ...(res ? res.newState : {}),
10
11
  ...req.actionData(),
11
- ...(res ? res.data : {})
12
+ ...(res ? res.data : {}),
13
+ c,
14
+ configuration: c
12
15
  };
13
16
  };