8000 GitHub - unblu/unblu-middleware-lib: This library is used to create a middleware for the Unblu v8 platform (API v4), based on Spring Boot. It provides a set of tools and utilities to facilitate the integration with Unblu services.
[go: up one dir, main page]

Skip to content

This library is used to create a middleware for the Unblu v8 platform (API v4), based on Spring Boot. It provides a set of tools and utilities to facilitate the integration with Unblu services.

License

Notifications You must be signed in to change notification settings

unblu/unblu-middleware-lib

Repository files navigation

Unblu middleware lib

This library is used to create a middleware for the Unblu v8 platform (API v4), based on Spring Boot. It provides a set of tools and utilities to facilitate the integration with Unblu services.

Usage example: example-middleware

Main features:

  • Provides beans for Unblu API clients - you can simply just inject conversationApi, webhooksApi, botsApi, etc. where needed. These are conditionally created only if bean with the same name doesn’t already exist, so you can override them if needed.

  • Automatically creates, manages and heals bot and/or webhook registrations

  • Correctly handles all registered webhooks, bot outbound requests and pings

  • Uses reactor to handle backpressure, supports options for asynchronous processing, as well as order guarantees where needed

  • Provides utility methods to define webhook handlers, declare conditions to accept boarding requests, or allowing you to react on different types of dialog bot outbound requests

Quick start - webhooks

Add a dependency to your build.gradle file:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'com.unblu.middleware:unblu-middleware-lib:1.10.2'
    implementation 'com.unblu.openapi:jersey3-client-v4:8.24.0'
    implementation 'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'
}

Configure the Unblu connection in your application.yml file. (You can also include unblu.bot settings, if you want to handle bots in the same middleware app.)

unblu:
  host: https://some-installation.unblu.com
  user: some-unblu-admin-user
  password: hello-im-some-unblu-admin-user-password
  middleware:
    url: https://where.my.middleware.is.running
    name: Test middleware
    description: Does stuff
  webhook:
    secret: hello-im-secure

Annotate your service (or middleware app) with @Import(UnbluWebhooks.class). This brings all necessary beans into your application context. (You can also use UnbluDialogBot.class: @Import({UnbluWebhooks.class, UnbluDialogBot.class}) if you want to handle bots in the same middleware app).

Use webhookHandler.handleWebhook() to process incoming webhooks.

@Service
@RequiredArgsConstructor
@Import(UnbluWebhooks.class)
public class MyAwesomeMiddlewareService implements ApplicationRunner {

    private final webhookHandler webhookHandler;

    @Override
    public void run(ApplicationArguments args) {
        // log every message sent anywhere using a webhook handler
        webhookHandler.onWebhook(eventName("conversation.new_message"), ConversationNewMessageEvent.class,
                e -> Mono.fromRunnable(() -> log.info("Message received: {}", e.getConversationMessage().getFallbackText())));
    }
}

Quick start - dialog bots

Add a dependency to your build.gradle file:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'com.unblu.middleware:unblu-middleware-lib:1.10.2'
    implementation 'com.unblu.openapi:jersey3-client-v4:8.24.0'
    implementation 'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'
}

Configure the Unblu connection in your application.yml file.

unblu:
  host: https://some-installation.unblu.com
  user: some-unblu-admin-user
  password: hello-im-some-unblu-admin-user-password
  middleware:
    url: https://where.my.middleware.is.running
    name: Test middleware
    description: Does stuff
  outboundRequests:
    secret: hello-im-secure
  bot:
    onboardingFilter: VISITORS
    person:
      firstName: Test
      lastName: Bot
      sourceId: Test middleware

Annotate your service (or middleware app) with @Import(UnbluDialogBot.class).

Use dialogBotService.accept…​OfferIf() to define conditions for accepting dialog offers. Every offer is rejected by default. Use dialogBotService.onDialog…​() methods to react on dialog events.

@Service
@RequiredArgsConstructor
@Import(UnbluBots.class)
public class MyAwesomeMiddlewareService implements ApplicationRunner {

    private final DialogBotService dialogBotService;

    @Override
    public void run(ApplicationArguments args) {
        // accept every onboarding offer
        dialogBotService.acceptOnboardingOfferIf(_o -> Mono.just(true));

        // greet the user when a dialog is opened
        dialogBotService.onDialogOpen(r ->
                Mono.fromRunnable(() -> sendMessage(r.getDialogToken(), "Hello, I am a bot!")));

        // echo every message back to the user
        dialogBotService.onDialogMessage(r ->
                Mono.fromRunnable(() -> echoIfSentByHuman(r)));
    }

    private void echoIfSentByHuman(BotDialogMessageRequest r) {
        if (r.getConversationMessage().getSenderPerson().getPersonType() == EPersonType.VISITOR) {
            sendMessage(r.getDialogToken(), "You wrote: " + r.getConversationMessage().getFallbackText());
        }
    }

    private void sendMessage(String dialogToken, String text) {
        try {
            botsApi.botsSendDialogMessage(new BotDialogPostMessage()
                    .dialogToken(dialogToken)
                    .messageData(new TextPostMessageData().text(text)));
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
    }
}

Configuration options

unblu:
  host: https://some-installation.unblu.com # mandatory, set me
  user: superadmin                  # mandatory, set me
  password: hello-im-superadmin-password  # mandatory, set me
  apiBasePath: /app/rest/v4   # this is the default
  idPropagationHeaderName:
  idPropagationUserId:       # content of the id propagation header

  middleware:
    url: https://where.my.middleware.is.running
    name: Test middleware
    description: This is a test middleware # optional, but recommended
    selfHealingEnabled: true  # see below, this is the default
    selfHealingCheckIntervalInSeconds: 60 # see below, this is the default
    autoSubscribe: true # if true, the middleware will automatically subscribe to webhooks and bots after startup, otherwise you need to call webhookHandler.subscribe() and dialogBotService.subscribe() manually or retrieve the Flux and subscribe to it yourself

  webhook:
    secret: another-secure-secret  # mandatory if webhooks are used
    cleanPrevious: false # see below, this is the default
    eventNames:    # useful to specify but not needed - event names passed to onWebhook are registered on the fly
      - conversation.onboarding
      - conversation.new_message

  outboundRequests:
    secret: a-secure-secret  # mandatory if services requiring outbound requests (e.g. dialog bots) are used
    apiPath: /outbound   # api path used by the middleware outbound controller, e.g. https://where.my.middleware.is.running/webhook. /outbound this is the default

  bot:
    person:
      firstName: Test                      # mandatory if bots are used
      lastName: Bot                        # mandatory if bots are used
      sourceId: Test middleware            # mandatory if bots are used

    cleanPrevious: false       # see below, this is the default
    onboardingFilter: VISITORS # can be VISITORS, AGENTS, BOTH, or NONE (default)
    onboardingOrder: 100       # this is the default
    offboardingFilter: NONE                # can be VISITORS, AGENTS, BOTH, or NONE (default)
    offboardingOrder: 100                  # this is the default
    reboardingEnabled: false               # this is the default
    reboardingOrder: 100                   # this is the default
    automaticTypingStateHandlingEnabled: true  # this is the default
    messageStateHandledExternally: false  # this is the default
    needsCounterpartPresence: true        # this is the default
    timeoutInMilliSeconds: 1000           # this is the default
    onTimeoutBehavior: ABORT              # can be HAND_OFF or ABORT (default)
    retryCount: 3                         # 0-5
    retryDelayInMilliSeconds: 1000        # 0-10000

cleanPrevious: false means that the registration will update the existing webhook registration, if it exists (register for given event names and activate). If you want to remove the previous registration and create a new one, set it to true.

This is useful when after a middleware restart, you don’t want to receive webhook events sent during the middleware downtime. Since Unblu hasn’t received a response to those webhooks, it will try to send them again.

selfHealingEnabled: true means that every selfHealingCheckIntervalInSeconds seconds, the middleware will check and perform repare actions if the webhook and bot registrations are still valid and correctly configured, in particular if they haven’t been auto-disabled by Unblu.

Subscribe

Note that you must subscribe to the fluxes in webhookHandler and outboundRequestHandler (used by dialogBotService).

You can do this by one of the following:

  • Setting unblu.middleware.autoSubscribe=true (default). Library then subscribes on ApplicationReadyEvent, so you must register your handlers before, e.g. in @PostConstruct or @Bean methods or in ApplicationRunner.run() method.

  • Calling .assertSubscribed() methods on the beans, e.g. webhookHandler.assertSubscribed() and dialogBotService.assertSubscribed() after registering your handlers. .assertSubscribed() guarantees you’re subscribed exactly once. You can also use explicit .subscribe(), then however you need to take care of double subscriptions.

  • Retrieving the fluxes (.getFlux()) and ensuring they are subscribed after registering your handlers.

Webhook handling

All webhookHandler webhook handling methods (processActions) should return a Mono<Void>. This allows the method to be asynchronous and non-blocking, which is essential for performance in a middleware context.

Processing order guarantees

Parameter requestOrderSpec determines what order guarantees the library should provide when processing webhooks. It can be one of the following:

  • RequestOrderSpec.canIgnoreOrder() - no order guarantees, the library will process webhooks as they arrive, without any specific order. This is the fastest option, allowing parallel processing of all webhooks.

  • RequestOrderSpec.mustPreserveOrder() - webhook handler functions (and Monos in them) will be called strictly in the order, in which the webhooks were received. You can still launch a parallel processing of an event e.g. by providing a Mono.fromRunnable().publishOn(Schedulers.parallel()) inside the handler function

  • RequestOrderSpec.mustPreserveOrderForThoseWithTheSame(…​) - webhook handler functions will be called in the order the webhooks were received, but only for the webhook calls that have the same value for the specified key. This allows you to process webhooks related to different entities in parallel, while still preserving the order for the same entity (e.g., conversation ID, branch ID, etc.). The entity id/key can be extracted from the event object using the lambda function passed.

Wrapped (with headers)

The processAction (but also other lambdas passed to the same .onWebhook() call) take the webhook event object as a parameter. In certain cases, headers of the request may be also important for processing (e.g. to propagate in the logback context - see below). For this purpose, the library provides a .onWrappedWebhook() method family, which allows you to access the headers of the request in your lambdas, in addition to the event object.

With logback context entries

As an optional last parameter, handling methods also allow you to pass a list of context entry specs, which allows you to populate the logback context with event-related information. Example usage:

webhookHandler.onWebhook(
        eventName("branch.branch"),
        BranchModificationEvent.class,
        e -> processBranchModified(e),
        canIgnoreOrder(),
        ContextSpec.of(
                "branchId", e -> e.getEntity().getId(),
                "method", _e -> "processBranchModified"
        )
)

logback.xml:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <pattern>{"message": "%message %ex","eventId": "%X{eventId}","branchId": "%X{branchId}","method": "%X{method}" ...}</pattern>
            </providers>
        </encoder>
    </appender>
    <springProfile name="production">
        <root level="info">
            <appender-ref ref="STDOUT"/>
        </root>
    </springProfile>
</configuration>

The following logback context variables are available for webhooks out-of-the-box (are always populated by the library):

  • eventId

  • deliveryId

  • retryNo

Dialog bots

Bots are a special type of middleware that can interact with Unblu dialogs and conversations. They can be used to automate tasks, provide information, or interact with users in a conversational manner. More details about bots can be found in the Unblu documentation - Bot integration. Currently, the middleware provides seemless support for dialog bots. Library provides means to implement conversation-observing bots through webhooks.

Accepting boarding offers

DialogBotService allows you to define which onboarding, offboarding, and reboarding offers the bot should accept. You can use the acceptOnboardingOfferIf(), acceptOffboardingOfferIf() and acceptReboardingOfferIf() methods to define conditions for accepting boarding offers. The methods take a function that returns a Mono<Boolean>, which determines whether the offer should be accepted or not. By default, no offers are accepted.

As an Unblu requirement, a bot needs to send a message after a dialog is open (after accepting an offer), before a configured timeout, otherwise it will be disabled by Unblu.

Reacting on dialog events

DialogBotService allows you to react to various dialog events, such as dialog opening, dialog messages, and dialog closing. The handlers take a function that gets the received request, passed as a parameter, and returns a Mono<Void>, which allows you to perform asynchronous operations in response to the event. As stated above, you need to implement at least onDialogOpen() and send a message in response (see the example app). You can call each function multiple times, e.g. to register handlers in different parts of your middleware application. Processing order of these handlers not guaranteed. Available methods are:

  • onDialogOpen() - called when a dialog is opened (after accepting an onboarding offer)

  • onDialogMessage() - called when a message is sent in a dialog

  • onDialogMessageState() - called when a message state is changed (e.g., when a message is read or delivered)

  • onDialogCounterpartChanged() - called when the counterpart of a dialog changes (e.g., when a user joins or leaves a dialog)

  • onDialogClose() - called when a dialog is closed

Processing order guarantees

Dialog bot handler guarantees the order of events for the same dialog token. This means that if you have multiple events for the same dialog, they will be processed in the order they were received. However, events for different dialogs can be processed in parallel.

Method flavors

Like the webhook handler, the dialog bot handler also provides a .onWrapped…​() method family, which allows you to access the headers of the request in your lambdas, in addition to the event object. The parameters are the same as for the webhook handler.

Also like the webhook handler, the dialog bot handler allows you to pass a list of context entry specs, which allows you to populate the logback context with event-related information. The usage is the same as for the webhook handler.

The following logback context variables are available for webhooks out-of-the-box (are always populated by the library):

  • dialogToken

  • conversationId

  • invocationId (for any outbound request)

  • deliveryId (for any outbound request)

  • retryNo (for any outbound request)

Outbound request handler API

The library also exposes a lower-level api OutboundRequestHandler, primarily intended for cases which aren’t yet explicitly covered by the library.

Outbound requests are implicitly imported with @Import(UnbluBots.class) but if you don’t use that annotation, you can import them explicitly with @Import(UnbluOutboundRequests.class). This exposes the outboundRequestHandler bean.

Outbound requests are handled similarly to webhooks, however require a proper response in the form of a class <Xxx>Response for each <Xxx>Request. Like with webhooks, the general practice is to respond as quickly as possible, and perform longer processing asynchronously. For outbound requests however, this may not always be possible, because an actual response with results of the handler operation is sometimes needed. For that reason, the outboundRequestHandler.registerHandler() provides a way to pass a synchronous lambda used to retrieve a Mono<XxxResponse>, and an asynchronous handler lambda which returns a Mono<Void>.

Example usage - this is how the dialog bot service implements onDialogOpen():

outboundRequestHandler.on(
        outboundRequestType("outbound.bot.dialog.opened"),
        BotDialogOpenRequest.class,
        BotDialogOpenResponse.class,
        _request -> Mono.just(new BotDialogOpenResponse())
                .doOnNext(_response -> log.debug("Responding to bot dialog open")),
        request -> Mono.fromRunnable(() -> sendMessage(request.getDialogToken(), "Hello, I am a bot!")));,
        mustPreserveOrderForThoseWithTheSame(request -> request.getDialogToken()),
        ContextSpec.of(
                "dialogToken", request -> request.getDialogToken()
        ));

sendMessage() here is an expensive asynchronous operation, so it is performed in the asynchronous handler lambda, while the synchronous lambda just returns an empty BotDialogOpenResponse as quickly as possible.

Method flavors

Like the webhook handler, the outbound request handler also provides a .onWrapped…​() method family, which allows you to access the headers of the request in your lambdas, in addition to the event object.

Also like the webhook handler, the dialog bot handler allows you to pass a list of context entry specs, which allows you to populate the logback context with event-related information. The usage is the same as for the webhook handler.

The following logback context variables are available for webhooks out-of-the-box (are always populated by the library):

  • invocationId

  • deliveryId

  • retryNo

Consume the middleware-lib from jitpack

The service https://jitpack.io/ is able to build any commit from any open source repo.

Warning
Jars on the jitpack repositories are not immutable (like on a SNAPSHOT respository) and the build of the jar is delegated to an external service. It is not recommended to use them in a middleware application that goes to production.

An additional repository has to be declared in the respositories section:

maven {
    url "https://jitpack.io"
    content {
        includeGroup "com.github.unblu"
    }
}

The coordinates are different:

  • GroupId: com.github.unblu

  • ArtifactId: unblu-middleware-lib (same as on maven central)

  • Version can be a commit (like e8f15d5ef4), a tag (like 1.8.1) or a branch name (like main-SNAPSHOT)

Example diff:

diff --git a/build.gradle b/build.gradle
index 3efa35a..87d9ceb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,8 +16,13 @@ java {
 }

 repositories {
+    maven {
+        url "https://jitpack.io"
+        content {
+            includeGroup "com.github.unblu"
+        }
+    }
     mavenCentral()
-    mavenLocal()
 }

 wrapper {
@@ -27,7 +32,7 @@ wrapper {

 dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-webflux'
-    implementation 'com.unblu.middleware:unblu-middleware-lib:1.8.0'
+    implementation 'com.github.unblu:unblu-middleware-lib:e8f15d5ef47da08724d806d5be5e08f18728095c'
     implementation 'com.unblu.openapi:jersey3-client-v4:8.24.0'
     implementation 'org.projectlombok:lombok:1.18.34'
     annotationProcessor 'org.projectlombok:lombok:1.18.34'

Troubleshoot a build on jitpack:

Next to the published artifact on the maven repo, jitpack is publishing a build.log file. Example: https://jitpack.io/com/github/unblu/unblu-middleware-lib/e8f15d5ef4/build.log

Troubleshooting

Configuration issues

Property must not be blank - you must populate required properties in your application.yml file, such as unblu.host, unblu.user, unblu.password, unblu.middleware.url, unblu.middleware.name, and either unblu.webhook.secret or unblu.bot.secret.

Errors during registration management

Errors during webhook registration management (typically 403 forbidden) are usually caused either by wrong Unblu credentials, or by using a non-admin Unblu user.

Handlers are not triggered

This is usually caused by not subscribing, see the "Subscribe" section above.

About

This library is used to create a middleware for the Unblu v8 platform (API v4), based on Spring Boot. It provides a set of tools and utilities to facilitate the integration with Unblu services.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages

0