@microsoft/botbuilder-m365
Version:
M365 extensions for Microsoft BotBuilder, Alpha release.
205 lines (191 loc) • 8.17 kB
text/typescript
/**
* @module botbuilder-m365
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
TurnContext,
TaskModuleTaskInfo,
ActivityTypes,
InvokeResponse,
INVOKE_RESPONSE_KEY,
TaskModuleResponse,
MessagingExtensionResult,
MessagingExtensionActionResponse,
Activity
} from 'botbuilder';
import { Application, RouteSelector } from './Application';
import { TurnState } from './TurnState';
const FETCH_INVOKE_NAME = `task/fetch`;
const SUBMIT_INVOKE_NAME = `task/submit`;
const DEFAULT_TASK_DATA_FILTER = 'verb';
export interface TaskModulesOptions {
taskDataFilter?: string;
}
export class TaskModules<TState extends TurnState> {
private readonly _app: Application<TState>;
public constructor(app: Application<TState>) {
this._app = app;
}
public fetch<TData>(
verb: string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[],
handler: (
context: TurnContext,
state: TState,
data: TData
) => Promise<TaskModuleTaskInfo | string>
): Application<TState> {
(Array.isArray(verb) ? verb : [verb]).forEach((v) => {
const filterField = this._app.options.taskModules?.taskDataFilter ?? DEFAULT_TASK_DATA_FILTER;
const selector = createTaskSelector(v, filterField, FETCH_INVOKE_NAME);
this._app.addRoute(
selector,
async (context, state) => {
// Insure that we're in an invoke as expected
if (
context?.activity?.type !== ActivityTypes.Invoke ||
context?.activity?.name !== FETCH_INVOKE_NAME
) {
throw new Error(
`Unexpected TaskModules.fetch() triggered for activity type: ${context?.activity?.type}`
);
}
// Call handler and then check to see if an invoke response has already been added
const result = await handler(context, state, context.activity.value?.data ?? {});
if (!context.turnState.get(INVOKE_RESPONSE_KEY)) {
// Format invoke response
let response: TaskModuleResponse;
if (typeof result == 'string') {
// Return message
response = {
task: {
type: 'message',
value: result
}
};
} else {
// Return card
response = {
task: {
type: 'continue',
value: result
}
};
}
// Queue up invoke response
await context.sendActivity({
value: { body: response, status: 200 } as InvokeResponse,
type: ActivityTypes.InvokeResponse
});
}
},
true
);
});
return this._app;
}
public submit<TData>(
verb: string | RegExp | RouteSelector | (string | RegExp | RouteSelector)[],
handler: (
context: TurnContext,
state: TState,
data: TData
) => Promise<TaskModuleTaskInfo | string | null | undefined>
): Application<TState> {
(Array.isArray(verb) ? verb : [verb]).forEach((v) => {
const filterField = this._app.options.taskModules?.taskDataFilter ?? DEFAULT_TASK_DATA_FILTER;
const selector = createTaskSelector(v, filterField, SUBMIT_INVOKE_NAME);
this._app.addRoute(
selector,
async (context, state) => {
// Insure that we're in an invoke as expected
if (
context?.activity?.type !== ActivityTypes.Invoke ||
context?.activity?.name !== SUBMIT_INVOKE_NAME
) {
throw new Error(
`Unexpected TaskModules.submit() triggered for activity type: ${context?.activity?.type}`
);
}
// Call handler and then check to see if an invoke response has already been added
const result = await handler(context, state, context.activity.value?.data ?? {});
if (!context.turnState.get(INVOKE_RESPONSE_KEY)) {
// Format invoke response
let response: TaskModuleResponse;
if (typeof result == 'string') {
// Return message
response = {
task: {
type: 'message',
value: result
}
};
} else if (typeof result == 'object') {
// Return card
response = {
task: {
type: 'continue',
value: result as TaskModuleTaskInfo
}
};
} else {
response = {
task: undefined
}
}
// Queue up invoke response
await context.sendActivity({
value: { body: response, status: 200 } as InvokeResponse,
type: ActivityTypes.InvokeResponse
});
}
},
true
);
});
return this._app;
}
}
/**
*
* @param {string | RegExp | RouteSelector[]} verb Name of the verb
* @param {string} filterField Name of the data field used to filter verbs
* @param {boolean} invokeName Name of the expected invoke activity
* @returns {RouteSelector} Route selector function
*/
function createTaskSelector(
verb: string | RegExp | RouteSelector,
filterField: string,
invokeName: string
): RouteSelector {
if (typeof verb == 'function') {
// Return the passed in selector function
return verb;
} else if (verb instanceof RegExp) {
// Return a function that matches the verb using a RegExp
return (context: TurnContext) => {
const isInvoke = context?.activity?.type == ActivityTypes.Invoke && context?.activity?.name == invokeName;
if (
isInvoke &&
typeof context?.activity?.value?.data == 'object' &&
typeof context.activity.value.data[filterField] == 'string'
) {
return Promise.resolve(verb.test(context.activity.value.data[filterField]));
} else {
return Promise.resolve(false);
}
};
} else {
// Return a function that attempts to match verb
return (context: TurnContext) => {
const isInvoke = context?.activity?.type == ActivityTypes.Invoke && context?.activity?.name == invokeName;
return Promise.resolve(
isInvoke &&
typeof context?.activity?.value?.data == 'object' &&
context.activity.value.data[filterField] == verb
);
};
}
}