0.0.43 • Published 4 years ago

aura-la-bot-sdk v0.0.43

Weekly downloads
-
License
ISC
Repository
github
Last release
4 years ago

aura-la-bot-sdk

SDK for building Living Apps bots

1. Usage

1.1. Install

Set up the dependencies in package.json as follows:

"dependencies": {
    "aura-la-bot-sdk": "^1.0.0",
    "botbuilder": "^3.15.0",
    "joi": "^14.3.1"
}

In case the developer team needs to be supplied with the tarball you can install it leaving the package.json as shown right above with the npm i aura-la-bot-sdk-M.m.r.tgz --no-save command.

1.2. Initialization

The Living App using this SDK needs to be loaded into AuraBot using the following function in index.ts

export = function setup(options: any, imports: any, register: (err: Error, result: any) => void) {
    const dialogs = [
        './dialogs/dialog-la-start',
        './dialogs/dialog-la-close',
        './dialogs/dialog-offer-dates',
        './dialogs/dialog-offer-flights',
        './dialogs/dialog-offers',
        './dialogs/dialog-search',
        './dialogs/dialog-call',
        './dialogs/dialog-web'
    ];

    // Remove lib dialogs based on options
    sdk.loader.excludeDialogs(dialogs, options);

    const settingsPath = path.resolve(__dirname, '..', 'settings');
    register(null, {
        [LIBRARY_NAME]: {
            dialogs: dialogs.map(d => require(d)),
            locale: sdk.loader.readLocaleFolder(path.resolve(settingsPath, 'locale')),
            env: sdk.loader.readEnv(settingsPath),
            config: sdk.loader.readDialogConfig(settingsPath),
            resources: path.resolve(__dirname, '..', 'resources')
        }
    });
};

1.3. Dialogs

The SDK simplifies the dialog model through the class LaDialog. The following is an example of what is needed for a dialog using this new class:

export default class SomeDialog extends LaDialog {
    // private fields for anything needed for the business logic
    private laConfig: LaConfig;

    // We supply the parent class with a string id (recommended the use of enums), and the SchemaMap and AuraBot that are automatically passed in when built. We also initialize any private field here.
    constructor(configurationSchema?: SchemaMap, bot?: AuraBot) {
        super(LIBRARY_NAME, DialogId.CALL, configurationSchema, bot);
        this.laConfig = new LaConfig(bot.configuration);
    }

    // This is the "message construction" stage of the dialog
    private async _dialogStage(session: Session, args: any, next: Function): Promise<void> {
        try {
            // Dispatch channel to currentDialog if needed
            if (sdk.lifecycle.getCallingIntent(session) === RedirectIntent.HOME) {
                sdk.lifecycle.checkUserIsWhitelisted(session, this.laConfig.getGlobalAuraIdsWhiteList());
                if (sdk.lifecycle.isChannelDispatched(session)) { return; }
            }
            sdk.lifecycle.initDialog(session, this.id);

            const backoffice = await this.laConfig.getBackoffice();
            const sessionData = sdk.lifecycle.getSessionData<SessionData>(session);

            //
            //  Business Logic goes here
            //

            // Generate response for all channels
            const screenData: any = {
                //
                //  Data for the channels goes here
                //
            };
            const isSpoken = true || false;
            const message = new ScreenMessage(Screen.OFFERS, screenData)
                    .withText('text', isSpoken);

            const choices = [ /* Prompt choices go here */ ];
            sdk.messaging.sendWithPrompt(session, message, choices, 'error.no_entity');
        } catch (error) {
            sdk.messaging.sendError(session, ErrorCode.SERVICE_ERROR, 'error.service', error);
        }
    }

    // This is the "response" stage of the dialog, from where we will change the current dialog or state
    private async _promptResponse(session: Session, result: IPromptChoiceResult, next: Function): Promise<void> {
        try {
            const sessionData = sdk.lifecycle.getSessionData<SessionData>(session);
            switch (sdk.lifecycle.getPromptResult(result)) {
                // Operations move from this dialog to another one linked to this
                case ChoiceOperation.BACK:
                    session.replaceDialog(DialogId.SOME_OTHER_DIALOG);
                    break;
                // Intents move from this dialog to any other dialog
                case RedirectIntent.HOME:
                    session.replaceDialog(DialogId.OFFERS);
                    break;
                case RedirectIntent.SEARCH:
                    session.replaceDialog(DialogId.SEARCH);
                    break;
                // This is a generic intent for closing the LA
                case 'intent.living-app.close':
                    session.replaceDialog('living-app:close-living-app');
                    break;
                // This is usually a not-defined operation/intent
                default:
                    sdk.messaging.sendError(session, ErrorCode.NO_ENTITY, 'error.no_entity');
                    session.replaceDialog(this.id);
                    break;
            }
        } catch (error) {
            sdk.messaging.sendError(session, ErrorCode.SERVICE_ERROR, 'error.service', error);
        }
    }

    // This will build the Bot Framework Waterfall
    protected dialogImplementation: Dialog = new WaterfallDialog([
        this._dialogStage.bind(this),
        this._promptResponse.bind(this)
    ]);
}

1.4. Logging

All dialogs have a logger this.logger automatically created when the dialog is built. It is also possible to add loggers in other places with the SDK wrapped logger LaLogger.

const logger = new LaLogger('la.library-name.logger-identifier');

The following methods are available to use:

logger.error({
    error: error.message, stck: error.stack,
    msg: `Error "${error.name}" doing something"`,
    corr: session.message.sourceEvent.correlator
});

logger.debug({
    msg: `Logging some DEBUG message`,
    corr: session.message.sourceEvent.correlator
});

logger.info({
    msg: `Logging some INFO message`,
    corr: session.message.sourceEvent.correlator
});

1.5. Base Api Client

The base api client LaApiClient is a small class designed to be extended. It is a small wrapper around request-promise-native, with a bit of the design of the BaseWsClient from TroyaBackend, albeit adapted to LAs needs. Note that this wrapper will log every request with all the information about url, parameters, body... as well as the responses, with headers. The logging will include correlator, thus requiring the class to be instanced inside the dialog steps.

  • Extend LaApiClient
  • Always call super(session, isMocked) in the constructor where isMocked is true if the client is to return mocked values and session is the botframework session (needs to be instanced inside a dialog, yeah).
  • declare your API calls, using the setupRequest(method, url, message) that is provided through this class

Example:

import { HTTPMethod, LaApiClient } from 'aura-la-bot-sdk';
import * as sdk from 'aura-la-bot-sdk';
import { Session } from 'botbuilder';

import { LaConfig } from '../config';
import { AUTH_API_RESPONSE } from './mocks-mythirdparty';

export class MyThirdPartyApiClient extends LaApiClient {
    private laConfig: LaConfig;

    constructor(laConfig: LaConfig, session: Session) {
        super(session, laConfig.getApiBaseUrl().indexOf('mock') > 0);
        this.laConfig = laConfig;
    }

    private async auth(): Promise<AuthResponse> {
        const msg = `Fetching access token with clientId: ${this.laConfig.getApiClientId()}`;
        const fetch: () => Promise<AuthResponse> = () =>
        this.setupRequest(HTTPMethod.GET, `${this.laConfig.getApiBaseUrl()}/v1/auth`, msg)
            .withQueryParameter('clientId', this.laConfig.getApiClientId())
            .withQueryParameter('clientSecret', this.laConfig.getApiClientSecret())
            .withHeader('Cache-Control', 'no-cache')
            .withTimeout(this.laConfig.getApiTimeout())
            .withMock(AUTH_API_RESPONSE)
            .execute<AuthResponse>();
        return sdk.cacheGet<AuthResponse>('<livingapp>.accessToken', fetch, (authResponse) => (authResponse.expires_in - 60), this.session);
    }
}

Request methods

You can apply the folowing methods to a request to configure it:

  • withCallback(callback: request.RequestCallback): LaRequest -> Allows passing a callback
  • withQueryParameter(key: string, value: string): LaRequest; -> Appends query parameters to the request
  • withHeader(key: string, value: any): LaRequest; -> Appends headers to the request
  • withAuthentication(auth: request.AuthOptions): LaRequest; -> request-promise auth options (e.g: { bearer: 'token' }
  • withOAuth(oauth: request.OAuthOptions): LaRequest; -> request-promise oauth options
  • withBody(body: any): LaRequest; -> Body for requests where it is needed
  • withRedirectFollow(follow: boolean): LaRequest; -> Sets or unsets the following of redirects
  • withEncoding(encoding: string | null): LaRequest; -> Sets the encoding of the response
  • withRawResponse(): LaRequest; -> Sets the request for raw mode (not json)
  • withTimeout(millis: number): LaRequest; -> applies a timeout to the request
  • withAdvancedOptions(options: rp.RequestPromiseOptions): LaRequest; -> Allows the input of requrest-promise options
  • withMock(mock: any): this; -> The mocked response for when the client is configured to return mock responses
  • execute(): Promise; -> Executes the request, returning a promise of type T (eg. any)

1.6. Method Reference

Cache

Declaration:

export declare function cacheGet<T>(
    key: string,
    fetch: () => Promise<T>,
    ttl: number | ((value: T) => number)
    session?: Session
): Promise<T>;

Usage:

import * as sdk from 'aura-la-bot-sdk';

sdk.cacheGet<SomeClass>(
    'cache_key',
    fetchPromise,
    number,
    session
);

sdk.cacheGet<SomeClass>(
    'cache_key',
    fetchPromise,
    (fetchResponse) => (fetchResponse.number),
    session
);

Lifecycle: Functions for messing around with LivingApp session data

  • getLaSession(session: Session) : AuraSession
  • getSessionData(session: Session): T
    • Gets a T subset of the session data, thus providing the way to split the interface as needed
  • getCallingIntent(session: Session): string
    • Gets the intent that reached the dialog. Eg. intent.operation.libraryname.back
  • getCallingEntities(session: Session): Entity[]
    • Gets the list of entities attached to the aura command.
  • getCallingEntity(session: Session, type: string, minScore: number = 0.4): string
    • Gets one of the entities attached to the aura command.
  • getPromptResult(result: IPromptChoiceResult, minScore: number = 0.4): string
    • Gets the result of the prompt (usually an intent).
  • getDialogId(session: Session) : string
  • initDialog(session: Session, dialogId: string): void
    • Called when a dialog starts, run some common logic like setting up current dialog id and access count
  • resetDialogAccessCount(session: Session, dialogId: string): void
    • Resets the dialog access count. Useful for restoring spoken messages conditions.
  • getDialogAccessCount(session: Session, dialogId: string): number
  • getAuraGlobalId(session: Session): string
  • getPhoneNumber(session: Session): string
  • checkUserIsWhitelisted(session: Session, whitelist: string[]): void
    • Checks if the user is whitelisted and redirects to the close dialog if not
  • isChannelDispatched(session: Session): boolean
    • Checks if the channel should be dispatched (and dispatch it if needed)
  • async getPersistedContext(session: Session): Promise<{key: string: any}>
    • Gets the living app user's persisted context
  • async persistContext(session: Session, context: {key: string: any}): Promise
    • Persists the context to database
  • sendEvent(session: Session, event: {key: string: any})
    • Sends an event or metric to the metric system

Messaging: Functions for sending messages to the channels

  • send(session: Session, msg: LaMessage, endDialog: boolean = true): void
    • Sends a message to all channels and ends the calling dialog if not told otherwise
  • sendWithPrompt(session: Session, msg: LaMessage, choices: string[], text?: string): void
    • Sends a prompt to all channels, with some choices (operations), and an optional text for an error message if it occurs.
  • sendToCurrentChannel(session: Session, msg: LaMessage, endDialog: boolean = true): void
    • Sends a message only to the current channel and ends the calling dialog if not told otherwise
  • sendError(session: Session, errorCode: ErrorCode, text?: string, error?: Error): void
    • Sends an error to all channels, with an optional text to be spoken, and an optional javacript error for logging purposes.

KGB Utils: Access to the QnA system

Use the LaKgbClient class to access the QnA utilities:

export declare class LaKgbClient {
    constructor(session: Session, bot: AuraBot, configKey?: string);
    getKgbResult(args: any): EntityKGB;
    getSuggestions(random?: boolean, currentSuggestion?: string): Promise<HighlightedFaq[]>;
}

In order to use it, you can instantiate and use its methods as follows:

const kgbClient = new LaKgbClient(session, this._bot);
const answerData: EntityKGB = kgbClient.getKgbResult(args);
const suggestions: HighlightedFaq[] = await kgbClient.getSuggestions(false, optionalSuggestion);

where the answerData is the answer to the question and the suggestions are the related questions as configured in the datasheet. The optionalSuggestion parameters allows to get linked suggestions instead of the default ones. The boolean parameter is used for randomization.

The answers follow the following model:

export declare enum EntityKGBType {
    TEXT = "text",
    TEXT_IMG = "textImg",
    IMG = "img",
    VIDEO = "video",
    TEXT_VIDEO = "textVideo",
    QR = "qr"
}
export interface EntityKGB {
    score: number;
    answer: string;
    questions: string;
    audio?: string;
    video?: string;
    image?: string;
    speak?: string;
    qr?: {
        text: string;
        url: string;
    };
    mainContent?: EntityKGBType;
}

Loader: Functions for registering the Living App with Aura (used in index.ts)

  • excludeDialogs(dialogNames: string[], options: any): void (wrapped)
    • excludes dialogs based on the options parameter
  • readLocaleFolder(localePath: string): any (wrapped)
  • readEnv(envPath: string): any (wrapped)
  • readDialogConfig(configPath: string): any (wrapped)

Utils: Functions for doing useful things

  • equalsIgnoreCase(a: string, b: string): boolean
    • True if equals except casing
  • equalsIgnoreAccents(a: string, b: string): boolean
    • True if equals except casing & accents
  • shuffleArray(array: any[]): any[]
    • Returns a shuffled copy of the array
  • getRandomElement(array: any[]): any
    • Returns a random element of the array
  • resolveNlpNumber(received: string, regExp: RegExp = /(a-zA-Z1234567890áéíóú+)/i): string[]
    • Returns a number from a human spoken text number
  • currentDateInTimezone(tz: string): string
  • toTimezone(date: string|Date, tz: string): Date
  • toYYYY_MM_DD(date: Date): string
0.0.43

4 years ago

0.0.42

4 years ago

0.0.41

4 years ago

0.0.40

4 years ago

0.0.39

4 years ago

0.0.38

4 years ago

0.0.37

4 years ago

0.0.36

4 years ago

0.0.34

4 years ago

0.0.35

4 years ago

0.0.33

4 years ago

0.0.32

4 years ago

0.0.31

4 years ago

0.0.30

4 years ago

0.0.24

4 years ago

0.0.25

4 years ago

0.0.26

4 years ago

0.0.27

4 years ago

0.0.28

4 years ago

0.0.29

4 years ago

0.0.23

4 years ago

0.0.22

4 years ago

0.0.21

4 years ago

0.0.20

4 years ago

0.0.19

4 years ago

0.0.11

4 years ago

0.0.12

4 years ago

0.0.13

4 years ago

0.0.14

4 years ago

0.0.15

4 years ago

0.0.16

4 years ago

0.0.17

4 years ago

0.0.18

4 years ago

0.0.3

4 years ago

0.0.10

4 years ago

0.0.9

4 years ago

0.0.8

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.2

4 years ago