@botonic/core 1.0.0-dev.0 → 1.0.0-dev.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.
Files changed (258) hide show
  1. package/lib/cjs/constants.d.ts +4 -15
  2. package/lib/cjs/constants.js +5 -16
  3. package/lib/cjs/constants.js.map +1 -1
  4. package/lib/cjs/core-bot.d.ts +12 -9
  5. package/lib/cjs/core-bot.js +54 -43
  6. package/lib/cjs/core-bot.js.map +1 -1
  7. package/lib/cjs/debug/index.d.ts +1 -1
  8. package/lib/cjs/debug/index.js +2 -6
  9. package/lib/cjs/debug/index.js.map +1 -1
  10. package/lib/cjs/debug/inspector.d.ts +1 -1
  11. package/lib/cjs/handoff.d.ts +5 -5
  12. package/lib/cjs/handoff.js +13 -10
  13. package/lib/cjs/handoff.js.map +1 -1
  14. package/lib/cjs/hubtype-service.d.ts +4 -4
  15. package/lib/cjs/hubtype-service.js +1 -1
  16. package/lib/cjs/hubtype-service.js.map +1 -1
  17. package/lib/cjs/i18n.d.ts +1 -1
  18. package/lib/cjs/index.d.ts +8 -163
  19. package/lib/cjs/index.js +8 -51
  20. package/lib/cjs/index.js.map +1 -1
  21. package/lib/cjs/models/bot-state.d.ts +11 -0
  22. package/lib/cjs/models/bot-state.js +3 -0
  23. package/lib/cjs/models/bot-state.js.map +1 -0
  24. package/lib/cjs/models/channels.d.ts +9 -0
  25. package/lib/cjs/models/channels.js +14 -0
  26. package/lib/cjs/models/channels.js.map +1 -0
  27. package/lib/cjs/models/events/base-event.d.ts +20 -0
  28. package/lib/cjs/models/events/base-event.js +17 -0
  29. package/lib/cjs/models/events/base-event.js.map +1 -0
  30. package/lib/cjs/models/events/botonic-event.d.ts +4 -0
  31. package/lib/cjs/models/events/botonic-event.js +3 -0
  32. package/lib/cjs/models/events/botonic-event.js.map +1 -0
  33. package/lib/{esm/models/events/connections/index.d.ts → cjs/models/events/connection/connection-event.d.ts} +1 -1
  34. package/lib/cjs/models/events/{connections/index.js → connection/connection-event.js} +1 -1
  35. package/lib/cjs/models/events/connection/connection-event.js.map +1 -0
  36. package/lib/cjs/models/events/connection/index.d.ts +1 -0
  37. package/lib/cjs/models/events/connection/index.js +5 -0
  38. package/lib/cjs/models/events/connection/index.js.map +1 -0
  39. package/lib/cjs/models/events/index.d.ts +4 -21
  40. package/lib/cjs/models/events/index.js +5 -8
  41. package/lib/cjs/models/events/index.js.map +1 -1
  42. package/lib/cjs/models/events/integration/index.d.ts +1 -0
  43. package/lib/cjs/models/events/integration/index.js +5 -0
  44. package/lib/cjs/models/events/integration/index.js.map +1 -0
  45. package/lib/cjs/models/events/integration/integration-event.d.ts +6 -0
  46. package/lib/cjs/models/events/integration/integration-event.js +3 -0
  47. package/lib/cjs/models/events/integration/integration-event.js.map +1 -0
  48. package/lib/cjs/models/events/message/carousel.d.ts +1 -1
  49. package/lib/cjs/models/events/message/custom.d.ts +2 -2
  50. package/lib/cjs/models/events/message/index.d.ts +9 -30
  51. package/lib/cjs/models/events/message/index.js +10 -29
  52. package/lib/cjs/models/events/message/index.js.map +1 -1
  53. package/lib/cjs/models/events/message/location.d.ts +1 -1
  54. package/lib/cjs/models/events/message/media.d.ts +1 -1
  55. package/lib/cjs/models/events/message/media.js +5 -5
  56. package/lib/cjs/models/events/message/media.js.map +1 -1
  57. package/lib/cjs/models/events/message/message-event.d.ts +30 -0
  58. package/lib/cjs/models/events/message/message-event.js +32 -0
  59. package/lib/cjs/models/events/message/message-event.js.map +1 -0
  60. package/lib/cjs/models/events/message/postback.d.ts +1 -1
  61. package/lib/cjs/models/events/message/text.d.ts +1 -1
  62. package/lib/cjs/models/index.d.ts +6 -0
  63. package/lib/cjs/models/index.js +10 -0
  64. package/lib/cjs/models/index.js.map +1 -0
  65. package/lib/cjs/models/legacy-types.d.ts +184 -0
  66. package/lib/cjs/models/legacy-types.js +59 -0
  67. package/lib/cjs/models/legacy-types.js.map +1 -0
  68. package/lib/cjs/models/session.d.ts +3 -0
  69. package/lib/cjs/models/session.js +3 -0
  70. package/lib/cjs/models/session.js.map +1 -0
  71. package/lib/cjs/models/user.d.ts +8 -4
  72. package/lib/cjs/output-parser/botonic-output-parser.d.ts +16 -0
  73. package/lib/cjs/output-parser/botonic-output-parser.js +37 -0
  74. package/lib/cjs/output-parser/botonic-output-parser.js.map +1 -0
  75. package/lib/cjs/output-parser/factory.d.ts +1 -1
  76. package/lib/cjs/output-parser/factory.js +7 -8
  77. package/lib/cjs/output-parser/factory.js.map +1 -1
  78. package/lib/cjs/output-parser/index.d.ts +3 -16
  79. package/lib/cjs/output-parser/index.js +4 -34
  80. package/lib/cjs/output-parser/index.js.map +1 -1
  81. package/lib/cjs/output-parser/parsers.d.ts +3 -9
  82. package/lib/cjs/output-parser/parsers.js +21 -12
  83. package/lib/cjs/output-parser/parsers.js.map +1 -1
  84. package/lib/cjs/plugins.d.ts +2 -3
  85. package/lib/cjs/plugins.js +9 -6
  86. package/lib/cjs/plugins.js.map +1 -1
  87. package/lib/cjs/routing/index.d.ts +2 -0
  88. package/lib/cjs/routing/index.js +6 -0
  89. package/lib/cjs/routing/index.js.map +1 -0
  90. package/lib/cjs/routing/router-utils.d.ts +11 -0
  91. package/lib/cjs/routing/router-utils.js +89 -0
  92. package/lib/cjs/routing/router-utils.js.map +1 -0
  93. package/lib/cjs/routing/router.d.ts +54 -0
  94. package/lib/cjs/routing/router.js +319 -0
  95. package/lib/cjs/routing/router.js.map +1 -0
  96. package/lib/cjs/utils.d.ts +1 -0
  97. package/lib/cjs/utils.js +7 -1
  98. package/lib/cjs/utils.js.map +1 -1
  99. package/lib/esm/constants.d.ts +4 -15
  100. package/lib/esm/constants.js +4 -15
  101. package/lib/esm/constants.js.map +1 -1
  102. package/lib/esm/core-bot.d.ts +12 -9
  103. package/lib/esm/core-bot.js +52 -41
  104. package/lib/esm/core-bot.js.map +1 -1
  105. package/lib/esm/debug/index.d.ts +1 -1
  106. package/lib/esm/debug/index.js +1 -1
  107. package/lib/esm/debug/index.js.map +1 -1
  108. package/lib/esm/debug/inspector.d.ts +1 -1
  109. package/lib/esm/handoff.d.ts +5 -5
  110. package/lib/esm/handoff.js +13 -10
  111. package/lib/esm/handoff.js.map +1 -1
  112. package/lib/esm/hubtype-service.d.ts +4 -4
  113. package/lib/esm/hubtype-service.js +1 -1
  114. package/lib/esm/hubtype-service.js.map +1 -1
  115. package/lib/esm/i18n.d.ts +1 -1
  116. package/lib/esm/index.d.ts +8 -163
  117. package/lib/esm/index.js +8 -45
  118. package/lib/esm/index.js.map +1 -1
  119. package/lib/esm/models/bot-state.d.ts +11 -0
  120. package/lib/esm/models/bot-state.js +2 -0
  121. package/lib/esm/models/bot-state.js.map +1 -0
  122. package/lib/esm/models/channels.d.ts +9 -0
  123. package/lib/esm/models/channels.js +11 -0
  124. package/lib/esm/models/channels.js.map +1 -0
  125. package/lib/esm/models/events/base-event.d.ts +20 -0
  126. package/lib/esm/models/events/base-event.js +14 -0
  127. package/lib/esm/models/events/base-event.js.map +1 -0
  128. package/lib/esm/models/events/botonic-event.d.ts +4 -0
  129. package/lib/esm/models/events/botonic-event.js +2 -0
  130. package/lib/esm/models/events/botonic-event.js.map +1 -0
  131. package/lib/{cjs/models/events/connections/index.d.ts → esm/models/events/connection/connection-event.d.ts} +1 -1
  132. package/lib/esm/models/events/{connections/index.js → connection/connection-event.js} +1 -1
  133. package/lib/esm/models/events/connection/connection-event.js.map +1 -0
  134. package/lib/esm/models/events/connection/index.d.ts +1 -0
  135. package/lib/esm/models/events/connection/index.js +2 -0
  136. package/lib/esm/models/events/connection/index.js.map +1 -0
  137. package/lib/esm/models/events/index.d.ts +4 -21
  138. package/lib/esm/models/events/index.js +4 -7
  139. package/lib/esm/models/events/index.js.map +1 -1
  140. package/lib/esm/models/events/integration/index.d.ts +1 -0
  141. package/lib/esm/models/events/integration/index.js +2 -0
  142. package/lib/esm/models/events/integration/index.js.map +1 -0
  143. package/lib/esm/models/events/integration/integration-event.d.ts +6 -0
  144. package/lib/esm/models/events/integration/integration-event.js +2 -0
  145. package/lib/esm/models/events/integration/integration-event.js.map +1 -0
  146. package/lib/esm/models/events/message/carousel.d.ts +1 -1
  147. package/lib/esm/models/events/message/custom.d.ts +2 -2
  148. package/lib/esm/models/events/message/index.d.ts +9 -30
  149. package/lib/esm/models/events/message/index.js +9 -28
  150. package/lib/esm/models/events/message/index.js.map +1 -1
  151. package/lib/esm/models/events/message/location.d.ts +1 -1
  152. package/lib/esm/models/events/message/media.d.ts +1 -1
  153. package/lib/esm/models/events/message/media.js +1 -1
  154. package/lib/esm/models/events/message/media.js.map +1 -1
  155. package/lib/esm/models/events/message/message-event.d.ts +30 -0
  156. package/lib/esm/models/events/message/message-event.js +29 -0
  157. package/lib/esm/models/events/message/message-event.js.map +1 -0
  158. package/lib/esm/models/events/message/postback.d.ts +1 -1
  159. package/lib/esm/models/events/message/text.d.ts +1 -1
  160. package/lib/esm/models/index.d.ts +6 -0
  161. package/lib/esm/models/index.js +7 -0
  162. package/lib/esm/models/index.js.map +1 -0
  163. package/lib/esm/models/legacy-types.d.ts +184 -0
  164. package/lib/esm/models/legacy-types.js +56 -0
  165. package/lib/esm/models/legacy-types.js.map +1 -0
  166. package/lib/esm/models/session.d.ts +3 -0
  167. package/lib/esm/models/session.js +2 -0
  168. package/lib/esm/models/session.js.map +1 -0
  169. package/lib/esm/models/user.d.ts +8 -4
  170. package/lib/esm/output-parser/botonic-output-parser.d.ts +16 -0
  171. package/lib/esm/output-parser/botonic-output-parser.js +33 -0
  172. package/lib/esm/output-parser/botonic-output-parser.js.map +1 -0
  173. package/lib/esm/output-parser/factory.d.ts +1 -1
  174. package/lib/esm/output-parser/factory.js +1 -2
  175. package/lib/esm/output-parser/factory.js.map +1 -1
  176. package/lib/esm/output-parser/index.d.ts +3 -16
  177. package/lib/esm/output-parser/index.js +3 -32
  178. package/lib/esm/output-parser/index.js.map +1 -1
  179. package/lib/esm/output-parser/parsers.d.ts +3 -9
  180. package/lib/esm/output-parser/parsers.js +14 -7
  181. package/lib/esm/output-parser/parsers.js.map +1 -1
  182. package/lib/esm/plugins.d.ts +2 -3
  183. package/lib/esm/plugins.js +9 -6
  184. package/lib/esm/plugins.js.map +1 -1
  185. package/lib/esm/routing/index.d.ts +2 -0
  186. package/lib/esm/routing/index.js +3 -0
  187. package/lib/esm/routing/index.js.map +1 -0
  188. package/lib/esm/routing/router-utils.d.ts +11 -0
  189. package/lib/esm/routing/router-utils.js +79 -0
  190. package/lib/esm/routing/router-utils.js.map +1 -0
  191. package/lib/esm/routing/router.d.ts +54 -0
  192. package/lib/esm/routing/router.js +315 -0
  193. package/lib/esm/routing/router.js.map +1 -0
  194. package/lib/esm/utils.d.ts +1 -0
  195. package/lib/esm/utils.js +5 -0
  196. package/lib/esm/utils.js.map +1 -1
  197. package/package.json +6 -6
  198. package/src/constants.ts +6 -15
  199. package/src/core-bot.ts +84 -47
  200. package/src/debug/index.ts +1 -6
  201. package/src/debug/inspector.ts +1 -1
  202. package/src/handoff.ts +15 -12
  203. package/src/hubtype-service.ts +5 -5
  204. package/src/i18n.ts +1 -1
  205. package/src/index.ts +8 -230
  206. package/src/models/bot-state.ts +12 -0
  207. package/src/models/channels.ts +9 -0
  208. package/src/models/events/base-event.ts +22 -0
  209. package/src/models/events/botonic-event.ts +26 -0
  210. package/src/models/events/{connections/index.ts → connection/connection-event.ts} +1 -1
  211. package/src/models/events/connection/index.ts +1 -0
  212. package/src/models/events/index.ts +4 -39
  213. package/src/models/events/integration/index.ts +1 -0
  214. package/src/models/events/integration/integration-event.ts +13 -0
  215. package/src/models/events/message/carousel.ts +1 -1
  216. package/src/models/events/message/custom.ts +2 -2
  217. package/src/models/events/message/index.ts +9 -37
  218. package/src/models/events/message/location.ts +1 -1
  219. package/src/models/events/message/media.ts +1 -1
  220. package/src/models/events/message/message-event.ts +37 -0
  221. package/src/models/events/message/postback.ts +1 -1
  222. package/src/models/events/message/text.ts +1 -1
  223. package/src/models/index.ts +6 -0
  224. package/src/models/legacy-types.ts +262 -0
  225. package/src/models/session.ts +3 -0
  226. package/src/models/user.ts +10 -4
  227. package/src/output-parser/botonic-output-parser.ts +38 -0
  228. package/src/output-parser/factory.ts +1 -3
  229. package/src/output-parser/index.ts +3 -38
  230. package/src/output-parser/parsers.ts +29 -17
  231. package/src/plugins.ts +18 -8
  232. package/src/routing/index.ts +2 -0
  233. package/src/routing/router-utils.ts +100 -0
  234. package/src/routing/router.ts +415 -0
  235. package/src/utils.ts +6 -0
  236. package/lib/cjs/errors.d.ts +0 -5
  237. package/lib/cjs/errors.js +0 -11
  238. package/lib/cjs/errors.js.map +0 -1
  239. package/lib/cjs/models/events/connections/index.js.map +0 -1
  240. package/lib/cjs/output-parser/util.d.ts +0 -2
  241. package/lib/cjs/output-parser/util.js +0 -14
  242. package/lib/cjs/output-parser/util.js.map +0 -1
  243. package/lib/cjs/router.d.ts +0 -35
  244. package/lib/cjs/router.js +0 -262
  245. package/lib/cjs/router.js.map +0 -1
  246. package/lib/esm/errors.d.ts +0 -5
  247. package/lib/esm/errors.js +0 -7
  248. package/lib/esm/errors.js.map +0 -1
  249. package/lib/esm/models/events/connections/index.js.map +0 -1
  250. package/lib/esm/output-parser/util.d.ts +0 -2
  251. package/lib/esm/output-parser/util.js +0 -9
  252. package/lib/esm/output-parser/util.js.map +0 -1
  253. package/lib/esm/router.d.ts +0 -35
  254. package/lib/esm/router.js +0 -258
  255. package/lib/esm/router.js.map +0 -1
  256. package/src/errors.ts +0 -11
  257. package/src/output-parser/util.ts +0 -8
  258. package/src/router.ts +0 -316
package/src/plugins.ts CHANGED
@@ -1,5 +1,11 @@
1
- import { Input, PluginConfig, Session } from './index'
2
- import { BotonicEvent } from './models/events'
1
+ import {
2
+ BotonicEvent,
3
+ BotState,
4
+ Input,
5
+ PluginConfig,
6
+ RoutePath,
7
+ Session,
8
+ } from './models'
3
9
 
4
10
  type PluginMode = 'pre' | 'post'
5
11
 
@@ -26,21 +32,25 @@ export async function runPlugins(
26
32
  mode: PluginMode,
27
33
  input: Input,
28
34
  session: Session,
29
- lastRoutePath: string,
35
+ botState: BotState,
30
36
  response: string | null = null,
31
- parsedResponse: Partial<BotonicEvent>[] | null = null
37
+ messageEvents: Partial<BotonicEvent>[] | null = null,
38
+ dataProvider?: any // TODO: type as dataProvider
32
39
  ): Promise<void> {
33
40
  for (const key in plugins) {
34
41
  const p = await plugins[key]
35
42
  try {
36
- if (mode == 'pre') await p.pre({ input, session, lastRoutePath })
37
- if (mode == 'post')
43
+ if (mode === 'pre')
44
+ await p.pre({ input, session, botState, dataProvider, plugins })
45
+ if (mode === 'post')
38
46
  await p.post({
39
47
  input,
40
48
  session,
41
- lastRoutePath,
49
+ botState,
42
50
  response,
43
- parsedResponse,
51
+ messageEvents,
52
+ dataProvider,
53
+ plugins,
44
54
  })
45
55
  } catch (e) {
46
56
  console.log(e)
@@ -0,0 +1,2 @@
1
+ export * from './router'
2
+ export * from './router-utils'
@@ -0,0 +1,100 @@
1
+ import {
2
+ EMPTY_ACTION_PATH,
3
+ NOT_FOUND_PATH,
4
+ PATH_PAYLOAD_IDENTIFIER,
5
+ PATH_PAYLOAD_REGEXP,
6
+ } from '../constants'
7
+ import {
8
+ Action,
9
+ BotRequest,
10
+ Input,
11
+ Params,
12
+ PathParams,
13
+ Route,
14
+ Routes,
15
+ } from '../models'
16
+
17
+ export class NoMatchingRouteError extends Error {
18
+ input: Input
19
+ constructor(input: Input) {
20
+ super(
21
+ `No route found for input '${JSON.stringify(
22
+ input
23
+ )}' and no ${NOT_FOUND_PATH} route defined`
24
+ )
25
+ this.input = input
26
+ }
27
+ }
28
+
29
+ export function isPathPayload(payload?: string): boolean {
30
+ if (!payload) return false
31
+ const isPathPayload = PATH_PAYLOAD_REGEXP.exec(payload)
32
+ return Boolean(isPathPayload)
33
+ }
34
+
35
+ export function getPathParamsFromPathPayload(payload?: string): PathParams {
36
+ const defaultPathParams = {
37
+ path: null,
38
+ params: {},
39
+ }
40
+ if (!payload) return defaultPathParams
41
+ if (!isPathPayload(payload)) return defaultPathParams
42
+ try {
43
+ const pathWithParams = payload.split(PATH_PAYLOAD_IDENTIFIER)[1]
44
+ if (!pathWithParams) {
45
+ throw `${PATH_PAYLOAD_IDENTIFIER} is empty`
46
+ }
47
+ const [path, params] = pathWithParams.split('?')
48
+ return { path: path ?? null, params: pathParamsToParams(params) }
49
+ } catch (e) {
50
+ console.error('Error getting path and params from input.payload:', e)
51
+ return defaultPathParams
52
+ }
53
+ }
54
+
55
+ export function pathParamsToParams(pathParams?: string): Params {
56
+ if (!pathParams) return {}
57
+ try {
58
+ const params = {}
59
+ const searchParams = new URLSearchParams(pathParams)
60
+ for (const [key, value] of searchParams) {
61
+ params[key] = value
62
+ }
63
+ return params
64
+ } catch (e) {
65
+ return {}
66
+ }
67
+ }
68
+
69
+ export function getEmptyAction(childRoutes?: Route[]): Action {
70
+ if (!childRoutes) return null
71
+ const emptyActionRoute = childRoutes.find(r => r.path === EMPTY_ACTION_PATH)
72
+ if (!emptyActionRoute) return null
73
+ return emptyActionRoute.action
74
+ }
75
+
76
+ export function getNotFoundAction(input: Input, routes: Route[]): Action {
77
+ const notFoundActionRoute = routes.find(r => r.path === NOT_FOUND_PATH)
78
+ if (!notFoundActionRoute) throw new NoMatchingRouteError(input)
79
+ return notFoundActionRoute.action
80
+ }
81
+
82
+ export async function getComputedRoutes(
83
+ routes: Routes,
84
+ request: BotRequest
85
+ ): Promise<Route[]> {
86
+ if (routes instanceof Function) {
87
+ return await getComputedRoutes(await routes(request), request)
88
+ }
89
+ for (const [key, route] of Object.entries(routes) as any) {
90
+ if (route.childRoutes && route.childRoutes instanceof Function) {
91
+ routes[key].childRoutes = await getComputedRoutes(
92
+ await route.childRoutes(request),
93
+ request
94
+ )
95
+ } else {
96
+ routes[key] = route
97
+ }
98
+ }
99
+ return routes
100
+ }
@@ -0,0 +1,415 @@
1
+ import { NOT_FOUND_PATH } from '../constants'
2
+ import { RouteInspector } from '../debug/inspector'
3
+ import {
4
+ BotState,
5
+ Input,
6
+ MatchedValue,
7
+ Matcher,
8
+ MatchingProp,
9
+ Nullable,
10
+ ProcessInputResult,
11
+ Route,
12
+ RouteParams,
13
+ RoutePath,
14
+ RoutingState,
15
+ Session,
16
+ } from '../models'
17
+ import { cloneObject } from '../utils'
18
+ import {
19
+ getEmptyAction,
20
+ getNotFoundAction,
21
+ getPathParamsFromPathPayload,
22
+ isPathPayload,
23
+ } from './router-utils'
24
+
25
+ export class Router {
26
+ routes: Route[]
27
+ routeInspector: RouteInspector
28
+
29
+ constructor(
30
+ routes: Route[],
31
+ routeInspector: RouteInspector | undefined = undefined
32
+ ) {
33
+ this.routes = routes
34
+ this.routeInspector = routeInspector || new RouteInspector()
35
+ }
36
+
37
+ /**
38
+ * Processes an input and return a representation of the new bot state.
39
+ * The algorithm is splitted in two main parts:
40
+ * 1. Getting the current routing state.
41
+ * 2. Given a routing state, resolve the different possible scenarios and return the new bot state.
42
+ * The new bot state can return three type of actions:
43
+ * - action: an action directly resolved from a matching route
44
+ * - emptyAction: optional action that can exists or not only within childRoutes
45
+ * - fallbackAction: any other action that acts as a fallback (404, )
46
+ */
47
+
48
+ // eslint-disable-next-line complexity
49
+ processInput(
50
+ input: Input,
51
+ session: Session,
52
+ botState: BotState
53
+ ): ProcessInputResult {
54
+ botState.retries = botState.retries ?? 0
55
+
56
+ // 1. Getting the current routing state.
57
+ const {
58
+ currentRoute,
59
+ matchedRoute,
60
+ params,
61
+ isFlowBroken,
62
+ } = this.getRoutingState(input, session, botState)
63
+
64
+ const currentRoutePath = currentRoute?.path ?? null
65
+ const matchedRoutePath = matchedRoute?.path ?? null
66
+
67
+ // 2. Given a routing state, resolve the different possible scenarios and return the new bot state.
68
+
69
+ /**
70
+ * Redirect Scenario:
71
+ * We have matched a redirect route with a given redirection path, so we try to obtain the redirectionRoute with getRouteByPath.
72
+ * Independently of whether the redirectionRoute is found or not, the intent is to trigger a redirection which by definition breaks the flow, so retries are set to 0.
73
+ * It has preference over ignoring retries.
74
+ */
75
+ if (matchedRoute && matchedRoute.redirect) {
76
+ botState.retries = 0
77
+ const redirectionRoute = this.getRouteByPath(matchedRoute.redirect)
78
+ if (redirectionRoute) {
79
+ botState.lastRoutePath = matchedRoute.redirect
80
+ return {
81
+ action: redirectionRoute.action,
82
+ emptyAction: getEmptyAction(redirectionRoute.childRoutes),
83
+ fallbackAction: null,
84
+ botState,
85
+ params,
86
+ }
87
+ }
88
+ botState.lastRoutePath = null
89
+ return {
90
+ action: null,
91
+ emptyAction: null,
92
+ fallbackAction: getNotFoundAction(input, this.routes),
93
+ botState,
94
+ params,
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Ignore Retry Scenario:
100
+ * We have matched a route with an ignore retry, so we return directly the new bot state. The intent is to break the flow, so retries are set to 0.
101
+ */
102
+ if (matchedRoute && matchedRoute.ignoreRetry) {
103
+ botState.retries = 0
104
+ botState.lastRoutePath = matchedRoutePath
105
+ return {
106
+ action: matchedRoute.action,
107
+ emptyAction: getEmptyAction(matchedRoute.childRoutes),
108
+ fallbackAction: null,
109
+ botState,
110
+ params,
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Retry Scenario:
116
+ * We were in a route which had retries enabled, so we check if the number of retries is exceeded.
117
+ * If we have not surpassed the limit of retries and we haven't matched an ignoreRetry route, update them, and then return the new bot state.
118
+ */
119
+ if (
120
+ isFlowBroken &&
121
+ currentRoute &&
122
+ currentRoute.retry &&
123
+ botState.retries < currentRoute.retry
124
+ ) {
125
+ botState.retries = botState.retries !== 0 ? botState.retries + 1 : 1
126
+ botState.lastRoutePath = currentRoutePath
127
+ if (matchedRoute && matchedRoutePath !== NOT_FOUND_PATH) {
128
+ return {
129
+ action: currentRoute.action,
130
+ emptyAction: getEmptyAction(matchedRoute.childRoutes),
131
+ fallbackAction: matchedRoute.action,
132
+ botState,
133
+ params,
134
+ }
135
+ }
136
+ return {
137
+ action: currentRoute.action ?? null,
138
+ emptyAction: getEmptyAction(currentRoute.childRoutes),
139
+ fallbackAction: getNotFoundAction(input, this.routes),
140
+ botState,
141
+ params,
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Default Scenario:
147
+ * We have matched a route or not, but we don't need to execute retries logic, so retries stay to 0.
148
+ */
149
+ botState.retries = 0
150
+
151
+ /**
152
+ * Matching Route Scenario:
153
+ * We have matched a route, so we return the new bot state.
154
+ */
155
+ if (matchedRoute && matchedRoutePath !== NOT_FOUND_PATH) {
156
+ botState.lastRoutePath = matchedRoutePath
157
+ return {
158
+ action: matchedRoute.action ?? null,
159
+ emptyAction: getEmptyAction(matchedRoute.childRoutes),
160
+ fallbackAction: null,
161
+ botState,
162
+ params,
163
+ }
164
+ }
165
+
166
+ /**
167
+ * 404 Scenario (No Route Found):
168
+ * We have not matched any route, so we return the new bot state.
169
+ */
170
+ botState.lastRoutePath = currentRoutePath
171
+ return {
172
+ action: null,
173
+ emptyAction: null,
174
+ fallbackAction: getNotFoundAction(input, this.routes),
175
+ params,
176
+ botState,
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Find the route that matches the given input, if it match with some of the entries, return the whole Route of the entry with optional params captured if matcher was a regex.
182
+ * IMPORTANT: It returns a cloned route instead of the route itself to avoid modifying original routes and introduce side effects
183
+ * */
184
+ getRoute(
185
+ input: Input | Partial<Input>,
186
+ routes: Route[],
187
+ session: Session,
188
+ lastRoutePath: RoutePath
189
+ ): RouteParams | null {
190
+ let params = {}
191
+ const route = routes.find(r =>
192
+ Object.entries(r)
193
+ .filter(
194
+ ([key, _]) =>
195
+ key !== 'action' && key !== 'childRoutes' && key !== 'path'
196
+ )
197
+ .some(([key, value]) => {
198
+ const match = this.matchRoute(
199
+ r,
200
+ key as MatchingProp,
201
+ value as Matcher,
202
+ input as Input,
203
+ session,
204
+ lastRoutePath
205
+ )
206
+ try {
207
+ if (match !== null && typeof match !== 'boolean' && match.groups) {
208
+ // Strip '[Object: null prototype]' from groups result: https://stackoverflow.com/a/62945609/6237608
209
+ params = { ...match.groups }
210
+ }
211
+ } catch (e) {}
212
+ return Boolean(match)
213
+ })
214
+ )
215
+ if (route) return { route: cloneObject(route), params }
216
+ return null
217
+ }
218
+
219
+ /**
220
+ * Find the route that matches the given path. Path can include concatenations, e.g: 'Flow1/Subflow1.1'.
221
+ * IMPORTANT: It returns a cloned route instead of the route itself to avoid modifying original routes and introduce side effects
222
+ * */
223
+ getRouteByPath(
224
+ path: RoutePath,
225
+ routeList: Route[] = this.routes
226
+ ): Nullable<Route> {
227
+ if (!path) return null
228
+ const [currentPath, ...childPath] = path.split('/')
229
+ for (const route of routeList) {
230
+ // iterate over all routeList
231
+ if (route.path === currentPath) {
232
+ if (
233
+ route.childRoutes &&
234
+ route.childRoutes.length &&
235
+ childPath.length > 0
236
+ ) {
237
+ // evaluate childroute over next actions
238
+ const computedRoute = this.getRouteByPath(
239
+ childPath.join('/'),
240
+ route.childRoutes
241
+ )
242
+ // IMPORTANT: Returning a new object to avoid modifying dev routes and introduce side effects
243
+ if (computedRoute) return cloneObject(computedRoute)
244
+ } else if (childPath.length === 0) {
245
+ return cloneObject(route) // last action and found route
246
+ }
247
+ }
248
+ }
249
+ return null
250
+ }
251
+
252
+ /**
253
+ * Returns the matched value for a specific route.
254
+ * Matching Props: ('text' | 'payload' | 'intent' | 'type' | 'input' | 'session' | 'request' ...)
255
+ * Matchers: (string: exact match | regex: regular expression match | function: return true)
256
+ * input: user input object, e.g.: {type: 'text', data: 'Hi'}
257
+ * */
258
+ matchRoute(
259
+ route: Route,
260
+ prop: MatchingProp,
261
+ matcher: Matcher,
262
+ input: Input,
263
+ session: Session,
264
+ lastRoutePath: RoutePath
265
+ ): MatchedValue {
266
+ let value: any = null
267
+ if (Object.keys(input).indexOf(prop) > -1) value = input[prop]
268
+ else if (prop === 'input') value = input
269
+ else if (prop === 'session') value = session
270
+ else if (prop === 'request') value = { input, session, lastRoutePath }
271
+ const matched = this.matchValue(matcher, value)
272
+ if (matched) {
273
+ this.routeInspector.routeMatched(route, prop, matcher, value)
274
+ } else {
275
+ this.routeInspector.routeNotMatched(route, prop, matcher, value)
276
+ }
277
+ return matched
278
+ }
279
+
280
+ /**
281
+ * Runs the matcher against the given value.
282
+ * If there is a match, it will return a truthy value (true, RegExp result), o.w., it will return a falsy value.
283
+ * */
284
+ matchValue(matcher: Matcher, value: any): MatchedValue {
285
+ if (typeof matcher === 'string') return value === matcher
286
+ if (matcher instanceof RegExp) {
287
+ if (value === undefined || value === null) return false
288
+ return matcher.exec(value)
289
+ }
290
+ if (typeof matcher === 'function') return matcher(value)
291
+ return false
292
+ }
293
+
294
+ /**
295
+ * It resolves the current state of navigation. Two scenarios:
296
+ * 1. Given a path payload input (__PATH_PAYLOAD__somePath?someParam=someValue), we can resolve the routing state directly from it (using getRouteByPath).
297
+ * 2. Given any other type of input, we resolve the routing state with normal resolution (using getRoute).
298
+ * */
299
+ getRoutingState(
300
+ input: Input,
301
+ session: Session,
302
+ botState: BotState
303
+ ): RoutingState {
304
+ const currentRoute = this.getRouteByPath(botState.lastRoutePath)
305
+ if (currentRoute && botState.lastRoutePath)
306
+ currentRoute.path = botState.lastRoutePath
307
+ if (typeof input.payload === 'string' && isPathPayload(input.payload)) {
308
+ return this.getRoutingStateFromPathPayload(currentRoute, input.payload)
309
+ }
310
+ return this.getRoutingStateFromInput(currentRoute, input, session)
311
+ }
312
+
313
+ /**
314
+ * Given a non path payload input, try to run it against the routes, update matching routes information in consequence and dictamine if the flow has been broken.
315
+ * */
316
+ getRoutingStateFromInput(
317
+ currentRoute: Nullable<Route>,
318
+ input: Input,
319
+ session: Session
320
+ ): RoutingState {
321
+ // get route depending of current ChildRoutes
322
+ if (currentRoute && currentRoute.childRoutes) {
323
+ const routeParams = this.getRoute(
324
+ input,
325
+ currentRoute.childRoutes,
326
+ session,
327
+ currentRoute.path
328
+ )
329
+ if (routeParams) {
330
+ return {
331
+ currentRoute,
332
+ matchedRoute: {
333
+ ...routeParams.route,
334
+ path:
335
+ routeParams.route && currentRoute.path
336
+ ? `${currentRoute.path}/${routeParams.route.path}`
337
+ : currentRoute.path,
338
+ },
339
+ params: routeParams.params,
340
+ isFlowBroken: false,
341
+ }
342
+ }
343
+ }
344
+ /**
345
+ * we couldn't find a route in the state of the currentRoute childRoutes,
346
+ * so let's find in the general routes
347
+ */
348
+ const routeParams = this.getRoute(
349
+ input,
350
+ this.routes,
351
+ session,
352
+ currentRoute?.path ?? null
353
+ )
354
+ const isFlowBroken = Boolean(currentRoute?.path)
355
+ if (routeParams?.route) {
356
+ return {
357
+ currentRoute,
358
+ matchedRoute: {
359
+ ...routeParams.route,
360
+ path: routeParams.route?.path ?? null,
361
+ },
362
+ params: routeParams.params,
363
+ isFlowBroken,
364
+ }
365
+ }
366
+ return {
367
+ currentRoute,
368
+ matchedRoute: null,
369
+ params: {},
370
+ isFlowBroken,
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Given a path payload input, try to run the path against the routes, update matching routes information in consequence and dictamine if the flow has been broken.
376
+ * */
377
+ getRoutingStateFromPathPayload(
378
+ currentRoute: Nullable<Route>,
379
+ pathPayload: string
380
+ ): RoutingState {
381
+ const { path, params } = getPathParamsFromPathPayload(pathPayload)
382
+ /**
383
+ * shorthand function to update the matching information given a path
384
+ */
385
+ const getRoutingStateFromPath = (seekPath: string): RoutingState => {
386
+ const matchedRoute = this.getRouteByPath(seekPath)
387
+ if (!matchedRoute) {
388
+ return {
389
+ currentRoute,
390
+ matchedRoute: null,
391
+ params: {},
392
+ isFlowBroken: true,
393
+ }
394
+ }
395
+ return {
396
+ currentRoute,
397
+ matchedRoute: { ...matchedRoute, path: seekPath },
398
+ params,
399
+ isFlowBroken: false,
400
+ }
401
+ }
402
+ /**
403
+ * Given a valid path: 'Flow1/Subflow1' we are in one of the two scenarios below.
404
+ */
405
+ // 1. Received __PATH_PAYLOAD__Subflow2, so we need to first try to concatenate it with Flow1 (lastRoutePath)
406
+ if (currentRoute?.path) {
407
+ const routingState = getRoutingStateFromPath(
408
+ `${currentRoute.path}/${path}`
409
+ )
410
+ if (routingState.matchedRoute) return routingState
411
+ }
412
+ // 2. Received __PATH_PAYLOAD__Flow1/Subflow1, so we can resolve it directly
413
+ return getRoutingStateFromPath(path as string)
414
+ }
415
+ }
package/src/utils.ts CHANGED
@@ -42,10 +42,16 @@ export const isMobile = (mobileBreakpoint = 460): boolean => {
42
42
  }
43
43
  return false
44
44
  }
45
+
45
46
  export function isFunction(o: any): boolean {
46
47
  return typeof o === 'function'
47
48
  }
48
49
 
50
+ export function cloneObject(object: any): any {
51
+ if (!object) return {}
52
+ return { ...object }
53
+ }
54
+
49
55
  export const params2queryString = (params: { [key: string]: string }): string =>
50
56
  Object.entries(params)
51
57
  .map(([k, v]: any) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
@@ -1,5 +0,0 @@
1
- import { Input } from './index';
2
- export declare class NoMatchingRouteError extends Error {
3
- input: Input;
4
- constructor(input: Input);
5
- }
package/lib/cjs/errors.js DELETED
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NoMatchingRouteError = void 0;
4
- class NoMatchingRouteError extends Error {
5
- constructor(input) {
6
- super(`No route found for input '${String(input)}' and no 404 route defined`);
7
- this.input = input;
8
- }
9
- }
10
- exports.NoMatchingRouteError = NoMatchingRouteError;
11
- //# sourceMappingURL=errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAEA,MAAa,oBAAqB,SAAQ,KAAK;IAE7C,YAAY,KAAY;QACtB,KAAK,CACH,6BAA6B,MAAM,CAAC,KAAK,CAAC,4BAA4B,CACvE,CAAA;QACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF;AARD,oDAQC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/models/events/connections/index.ts"],"names":[],"mappings":";;;AAEA,IAAY,uBAGX;AAHD,WAAY,uBAAuB;IACjC,kDAAuB,CAAA;IACvB,wDAA6B,CAAA;AAC/B,CAAC,EAHW,uBAAuB,GAAvB,+BAAuB,KAAvB,+BAAuB,QAGlC"}
@@ -1,2 +0,0 @@
1
- export declare function parseNumber(strNumber: string): number;
2
- export declare function parseBoolean(strNumber: string): boolean;
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseBoolean = exports.parseNumber = void 0;
4
- function parseNumber(strNumber) {
5
- return parseInt(strNumber);
6
- }
7
- exports.parseNumber = parseNumber;
8
- function parseBoolean(strNumber) {
9
- if (strNumber === '0')
10
- return false;
11
- return true;
12
- }
13
- exports.parseBoolean = parseBoolean;
14
- //# sourceMappingURL=util.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/output-parser/util.ts"],"names":[],"mappings":";;;AAAA,SAAgB,WAAW,CAAC,SAAiB;IAC3C,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC;AAFD,kCAEC;AAED,SAAgB,YAAY,CAAC,SAAiB;IAC5C,IAAI,SAAS,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACnC,OAAO,IAAI,CAAA;AACb,CAAC;AAHD,oCAGC"}
@@ -1,35 +0,0 @@
1
- import { RouteInspector } from './debug/inspector';
2
- import { Input, Route, Routes, Session } from './index';
3
- interface RouteParams {
4
- route: Route;
5
- params: any;
6
- }
7
- declare type MatchingProp = 'text' | 'payload' | 'intent' | 'type' | 'input' | 'session' | 'request';
8
- declare type Matcher = string | RegExp | ((args: any) => boolean);
9
- export declare class Router {
10
- routes: Routes;
11
- routeInspector: RouteInspector;
12
- lastRoutePath: string | null;
13
- /**
14
- * @param {Route[]} routes
15
- * @param routeInspector
16
- */
17
- constructor(routes: Routes, routeInspector?: RouteInspector | undefined);
18
- processInput(input: Input, session?: Partial<Session>, lastRoutePath?: string | null): any;
19
- getOnFinishParams(input: Input): string | undefined;
20
- /**
21
- * @return {null|RouteParams}
22
- */
23
- getRoute(input: Input | Partial<Input>, routes: Routes, session: Session, lastRoutePath: string | null): RouteParams | null;
24
- getRouteByPath(path: string | null, routeList?: Routes | null): Route | null;
25
- /**
26
- * @return {Params|boolean}
27
- */
28
- matchRoute(route: Route, prop: MatchingProp, matcher: Matcher, input: Input, session: Session, lastRoutePath: string | null): any;
29
- /**
30
- *
31
- * @return {*|boolean|Params}
32
- */
33
- matchValue(matcher: string | RegExp | ((args: any) => boolean), value: any): boolean;
34
- }
35
- export {};