From d9d7c63f7f651ca0248fb252c53d0a862f7a38a8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Apr 2022 14:23:44 +0200 Subject: [PATCH 01/12] :sparkles: Add optional payload to middleware functions --- framework/src/Middleware.ts | 7 ++++--- framework/src/MiddlewareCollection.ts | 25 +++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/framework/src/Middleware.ts b/framework/src/Middleware.ts index 6aa7704a39..b3bba83a56 100644 --- a/framework/src/Middleware.ts +++ b/framework/src/Middleware.ts @@ -1,6 +1,7 @@ import { Jovo } from './Jovo'; +import { AnyObject } from '@jovotech/common'; -export type MiddlewareFunction = (jovo: Jovo) => Promise | unknown; +export type MiddlewareFunction = (jovo: Jovo, payload?: AnyObject) => Promise | unknown; export class Middleware { readonly fns: MiddlewareFunction[]; @@ -15,12 +16,12 @@ export class Middleware { return this; } - async run(jovo: Jovo): Promise { + async run(jovo: Jovo, payload?: AnyObject): Promise { if (!this.enabled) { return; } for (let i = 0, len = this.fns.length; i < len; i++) { - await this.fns[i](jovo); + await this.fns[i](jovo, payload); } } diff --git a/framework/src/MiddlewareCollection.ts b/framework/src/MiddlewareCollection.ts index c10a6e03a6..a285a11018 100644 --- a/framework/src/MiddlewareCollection.ts +++ b/framework/src/MiddlewareCollection.ts @@ -1,4 +1,4 @@ -import { ArrayElement } from '@jovotech/common'; +import { AnyObject, ArrayElement } from '@jovotech/common'; import { Jovo } from './index'; import { Middleware, MiddlewareFunction } from './Middleware'; @@ -77,30 +77,39 @@ export class MiddlewareCollection { return this; } - async run(name: PossibleMiddlewareNames, jovo: Jovo): Promise; - async run(name: string, jovo: Jovo): Promise; - async run(names: PossibleMiddlewareNames[], jovo: Jovo): Promise; - async run(names: string[], jovo: Jovo): Promise; + async run( + name: PossibleMiddlewareNames, + jovo: Jovo, + payload?: AnyObject, + ): Promise; + async run(name: string, jovo: Jovo, payload?: AnyObject): Promise; + async run( + names: PossibleMiddlewareNames[], + jovo: Jovo, + payload?: AnyObject, + ): Promise; + async run(names: string[], jovo: Jovo, payload?: AnyObject): Promise; async run( nameOrNames: | string | PossibleMiddlewareNames | Array>, jovo: Jovo, + payload?: AnyObject, ): Promise { const names = typeof nameOrNames === 'string' ? [nameOrNames] : nameOrNames; for (const name of names) { const beforeName = `before.${name}`; if (this.has(beforeName)) { - await this.run(beforeName, jovo); + await this.run(beforeName, jovo, payload); } const middleware = this.get(name); - await middleware?.run(jovo); + await middleware?.run(jovo, payload); const afterName = `after.${name}`; if (this.has(afterName)) { - await this.run(afterName, jovo); + await this.run(afterName, jovo, payload); } } } From b4d07a66901922a71bcf248698ca92a0fa757410 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Apr 2022 14:24:36 +0200 Subject: [PATCH 02/12] :sparkles: Add payload to "EXECUTE_HANDLER_MIDDLEWARE" --- framework/src/ComponentTreeNode.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/ComponentTreeNode.ts b/framework/src/ComponentTreeNode.ts index 063a74e518..49ff34404f 100644 --- a/framework/src/ComponentTreeNode.ts +++ b/framework/src/ComponentTreeNode.ts @@ -78,7 +78,10 @@ export class ComponentTreeNode } // Run any middlewares that are attached to 'event.ComponentTreeNode.executeHandler' - await jovo.$handleRequest.middlewareCollection.run(EXECUTE_HANDLER_MIDDLEWARE, jovo); + await jovo.$handleRequest.middlewareCollection.run(EXECUTE_HANDLER_MIDDLEWARE, jovo, { + component: this.name, + handler, + }); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (componentInstance as any)[handler](...(callArgs || [])); From 7114ed4efda9d2c73b67689958e5e6a7d68ec162 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Apr 2022 14:33:41 +0200 Subject: [PATCH 03/12] :sparkles: Add payloads for middleware in $send, $redirect, $resolve --- framework/src/Jovo.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/framework/src/Jovo.ts b/framework/src/Jovo.ts index 735b0a58a6..b664ab5b93 100644 --- a/framework/src/Jovo.ts +++ b/framework/src/Jovo.ts @@ -331,7 +331,10 @@ export abstract class Jovo< // update the active component node in handleRequest to keep track of the state this.$handleRequest.activeComponentNode = componentNode; - await this.$handleRequest.middlewareCollection.run(REDIRECT_MIDDLEWARE, this); + await this.$handleRequest.middlewareCollection.run(REDIRECT_MIDDLEWARE, this, { + componentName, + handler, + }); // execute the component's handler await componentNode.executeHandler({ @@ -405,7 +408,10 @@ export abstract class Jovo< // update the active component node in handleRequest to keep track of the state this.$handleRequest.activeComponentNode = componentNode; - await this.$handleRequest.middlewareCollection.run(DELEGATE_MIDDLEWARE, this); + await this.$handleRequest.middlewareCollection.run(DELEGATE_MIDDLEWARE, this, { + componentName, + options, + }); // execute the component's handler await componentNode.executeHandler({ @@ -440,7 +446,11 @@ export abstract class Jovo< // update the active component node in handleRequest to keep track of the state this.$handleRequest.activeComponentNode = previousComponentNode; - await this.$handleRequest.middlewareCollection.run(RESOLVE_MIDDLEWARE, this); + await this.$handleRequest.middlewareCollection.run(RESOLVE_MIDDLEWARE, this, { + resolvedHandler, + eventName, + eventArgs, + }); // execute the component's handler await previousComponentNode.executeHandler({ From d0a4f93b8dd2dfe15879548510353bf2039246a9 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 May 2022 10:22:45 +0200 Subject: [PATCH 04/12] Revert pull_request trigger changes --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 8fafbad91d..52c531f9f7 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -1,7 +1,7 @@ name: jovo-framework workflow -on: [push] +on: [push, pull_request] jobs: build: From ae143dc7e750d3d60870d9dbcb245f9aaf0d6dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Wed, 11 May 2022 19:25:05 +0200 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=90=9B=20Clear=20state=20stack=20on?= =?UTF-8?q?=20`$redirect`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/src/Jovo.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/framework/src/Jovo.ts b/framework/src/Jovo.ts index 735b0a58a6..f9385cb713 100644 --- a/framework/src/Jovo.ts +++ b/framework/src/Jovo.ts @@ -314,18 +314,16 @@ export abstract class Jovo< this.$handleRequest.activeComponentNode?.path, ); - // update the state-stack if the component is not global + // clear the state stack + this.$session.state = []; + + // add new component to the stack if it's not global + // @see https://www.jovo.tech/docs/components#global-components if (!componentNode.metadata.isGlobal) { const stackItem: StateStackItem = { component: componentNode.path.join('.'), }; - if (!this.$state?.length) { - // initialize the state-stack if it is empty or does not exist - this.$session.state = [stackItem]; - } else { - // replace last item in stack - this.$state[this.$state.length - 1] = stackItem; - } + this.$session.state.push(stackItem); } // update the active component node in handleRequest to keep track of the state From 46cf8b2411149c8e7646d07acbf2af448804dc10 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 May 2022 17:39:38 +0200 Subject: [PATCH 06/12] :bug: Fixes memory leak in TestSuite #1309 --- framework/src/testsuite/TestSuite.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/framework/src/testsuite/TestSuite.ts b/framework/src/testsuite/TestSuite.ts index f664c4cc61..0ac7c62969 100644 --- a/framework/src/testsuite/TestSuite.ts +++ b/framework/src/testsuite/TestSuite.ts @@ -3,6 +3,7 @@ import { OutputTemplate } from '@jovotech/output'; import { existsSync } from 'fs'; import _cloneDeep from 'lodash.clonedeep'; import _merge from 'lodash.merge'; +import _set from 'lodash.set'; import { join as joinPaths } from 'path'; import { PartialDeep } from 'type-fest'; import { v4 as uuidv4 } from 'uuid'; @@ -183,13 +184,12 @@ export class TestSuite extends Plugin< : (requestLike as RequestOrInput); await this.app.initialize(); - + // const request: PlatformTypes['request'] = this.isRequest(this.requestOrInput) ? this.requestOrInput : this.requestOrInput.type === InputType.Launch ? this.requestBuilder.launch() : this.requestBuilder.intent(); - await this.app.handle(new TestServer(request)); } @@ -227,7 +227,10 @@ export class TestSuite extends Plugin< // Set session data jovo.$session.isNew = false; - Object.assign(this, jovo); + _merge(this.$user.data, jovo.$user.data); + _merge(this.$session, jovo.$session); + _merge(this.$response, jovo.$response); + _merge(this.$output, jovo.$output); } private loadApp(): App { From fb97c584804dd3000ad0cb8cd8d20f8ae3866c04 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 May 2022 17:42:07 +0200 Subject: [PATCH 07/12] :rotating_light: Fix linter warnings --- framework/src/testsuite/TestSuite.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/testsuite/TestSuite.ts b/framework/src/testsuite/TestSuite.ts index 0ac7c62969..c284030062 100644 --- a/framework/src/testsuite/TestSuite.ts +++ b/framework/src/testsuite/TestSuite.ts @@ -3,7 +3,6 @@ import { OutputTemplate } from '@jovotech/output'; import { existsSync } from 'fs'; import _cloneDeep from 'lodash.clonedeep'; import _merge from 'lodash.merge'; -import _set from 'lodash.set'; import { join as joinPaths } from 'path'; import { PartialDeep } from 'type-fest'; import { v4 as uuidv4 } from 'uuid'; @@ -184,7 +183,7 @@ export class TestSuite extends Plugin< : (requestLike as RequestOrInput); await this.app.initialize(); - // + const request: PlatformTypes['request'] = this.isRequest(this.requestOrInput) ? this.requestOrInput : this.requestOrInput.type === InputType.Launch From df9073a3c55b5ab68e16abcdbcf196063d4b1c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Mon, 16 May 2022 21:46:56 +0200 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=93=9D=20Update=20redirect=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/handlers.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/handlers.md b/docs/handlers.md index 151a41ca24..f5b3d5be7b 100644 --- a/docs/handlers.md +++ b/docs/handlers.md @@ -127,7 +127,7 @@ yourHandler() { ### Redirect to Components -If you `$redirect()` to a different component, the current one is removed from the [`$state` stack](./state-stack.md). You can see this as a permanent redirect. +If you `$redirect()` to a different component, the current [`$state` stack](./state-stack.md) is cleared. You can see this as a permanent redirect. We recommend redirects if you want to move from one isolated part (or component) of a conversation to another. If you want to keep the state when moving between components, we recommend using [`$delegate()`](#delegate-to-components). If no handler name is specified, the redirect triggers the other component's `START` handler. @@ -288,7 +288,6 @@ UNHANDLED() { By default, the current component's `UNHANDLED` gets prioritized over global handlers in other components. Learn more about [`UNHANDLED` prioritization in the routing documentation](./routing.md#unhandled-prioritization). - ## Middlewares The `event.ComponentTreeNode.executeHandler` [event middleware](./middlewares.md#event-middlewares) gets called every time a handler is executed. For example, you can [hook](./hooks.md) into it like this: From 9df9ce1b30e2cf883332e4c901c6ed069a273ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Tue, 17 May 2022 14:53:16 +0200 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=93=9D=20Add=20note=20about=20middl?= =?UTF-8?q?eware=20payload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/handlers.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/handlers.md b/docs/handlers.md index 151a41ca24..81a1078627 100644 --- a/docs/handlers.md +++ b/docs/handlers.md @@ -288,13 +288,17 @@ UNHANDLED() { By default, the current component's `UNHANDLED` gets prioritized over global handlers in other components. Learn more about [`UNHANDLED` prioritization in the routing documentation](./routing.md#unhandled-prioritization). - ## Middlewares The `event.ComponentTreeNode.executeHandler` [event middleware](./middlewares.md#event-middlewares) gets called every time a handler is executed. For example, you can [hook](./hooks.md) into it like this: ```typescript -app.hook('after.event.ComponentTreeNode.executeHandler', (jovo: Jovo): void => { +app.hook('after.event.ComponentTreeNode.executeHandler', (jovo: Jovo, payload): void => { // ... }); ``` + +The `payload` includes the following properties: + +- `componentName` +- `handler` From 430285ea6db7298f0d7eefc61bd6233603338aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Tue, 17 May 2022 14:53:29 +0200 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=93=9D=20Add=20section=20about=20cu?= =?UTF-8?q?stom=20middlewares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/middlewares.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/middlewares.md b/docs/middlewares.md index 7e95d5e77f..1edb937041 100644 --- a/docs/middlewares.md +++ b/docs/middlewares.md @@ -81,6 +81,24 @@ Find all current event middlewares in the table below: ## Middleware Features +### Custom Middlewares + +You can also use the `$handleRequest` object to run your own middlewares, for example: + +```typescript +await jovo.$handleRequest.middlewareCollection.run('', jovo, payload); +``` + +The `payload` is of the type `AnyObject`, so you can pass any object to the middleware, for example `{ name: 'SomeName' }`. + +Using a [hook](./hooks.md) or a [plugin](./plugins.md), you can then hook into this middleware: + +```typescript +app.hook('', async (jovo: Jovo, payload): Promise => { + // ... +}); +``` + ### Stop the Middleware Execution Either a [hook](./hooks.md) or a [plugin](./plugins.md) can use `stopMiddlewareExecution` to remove all middlewares from the middleware collection of `HandleRequest` and its plugins. This way, all following middlewares won't be executed. From 3cf6bc9c3c6b1bdd8f2b9aefbd28410c4085cfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Tue, 17 May 2022 15:08:46 +0200 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=93=9D=20Add=20payloads=20to=20midd?= =?UTF-8?q?leware=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/middlewares.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/middlewares.md b/docs/middlewares.md index 1edb937041..f73eb4ee01 100644 --- a/docs/middlewares.md +++ b/docs/middlewares.md @@ -69,15 +69,24 @@ There are two types of event middlewares: - Public methods like [`$resolve`](./handlers.md#resolve-a-component) can be accessed using `event.$resolve` - Some "under the hood" methods can be accessed using the class name and the method name, for example `event.ComponentTreeNode.executeHandler` +These middlewares can also come with a `payload` that you can access in your hook or plugin as second parameter, for example: + +```typescript +app.hook('event.$resolve', async (jovo: Jovo, payload): Promise => { + const resolvedHandler = payload.resolvedHandler; + // ... +}); +``` + Find all current event middlewares in the table below: -| Middleware | Description | -| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `event.$resolve` | [`$resolve`](./handlers.md#resolve-a-component) is called in a handler | -| `event.$redirect` | [`$redirect`](./handlers.md#redirect-to-components) is called in a handler | -| `event.$delegate` | [`$delegate`](./handlers.md#delegate-to-components) is called in a handler | -| `event.$send` | [`$send`](./output.md#send-a-message) is called in a handler | -| `event.ComponentTreeNode.executeHandler` | This event is called whenever a new handler gets executed. Part of the [`ComponentTreeNode` class](https://github.com/jovotech/jovo-framework/blob/v4/latest/framework/src/ComponentTreeNode.ts). See the [`ComponentTree` section](./components.md#componenttree) for more information. | +| Middleware | Description | Payload | +| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `event.$resolve` | [`$resolve`](./handlers.md#resolve-a-component) is called in a handler | `resolvedHandler: string`, `eventName: string`, `eventArgs: ARGS extends unknown[]` | +| `event.$redirect` | [`$redirect`](./handlers.md#redirect-to-components) is called in a handler | `componentName: string`, `handler: string` | +| `event.$delegate` | [`$delegate`](./handlers.md#delegate-to-components) is called in a handler | `componentName: string`, `options: DelegateOptions` | +| `event.$send` | [`$send`](./output.md#send-a-message) is called in a handler | `outputConstructorOrTemplateOrMessage`, `options` | +| `event.ComponentTreeNode.executeHandler` | This event is called whenever a new handler gets executed. Part of the [`ComponentTreeNode` class](https://github.com/jovotech/jovo-framework/blob/v4/latest/framework/src/ComponentTreeNode.ts). See the [`ComponentTree` section](./components.md#componenttree) for more information. | `componentName: string`, `handler: string` | ## Middleware Features From bb7e051cae58756a65af1b619ba0f6488d119625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ko=CC=88nig?= Date: Tue, 17 May 2022 15:12:10 +0200 Subject: [PATCH 12/12] =?UTF-8?q?=E2=9C=A8=20Add=20`$send`=20payload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/src/Jovo.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/Jovo.ts b/framework/src/Jovo.ts index b664ab5b93..740ce4b478 100644 --- a/framework/src/Jovo.ts +++ b/framework/src/Jovo.ts @@ -290,7 +290,10 @@ export abstract class Jovo< // push the new OutputTemplate(s) to $output Array.isArray(newOutput) ? this.$output.push(...newOutput) : this.$output.push(newOutput); - await this.$handleRequest.middlewareCollection.run(SEND_MIDDLEWARE, this); + await this.$handleRequest.middlewareCollection.run(SEND_MIDDLEWARE, this, { + outputConstructorOrTemplateOrMessage, + options, + }); } async $redirect<