type2docfx
Version:
A tool to convert json format output from TypeDoc to universal reference model for DocFx to consume.
277 lines (258 loc) • 12.4 kB
text/typescript
/**
* @module botbuilder-prompts
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
Promiseable, Activity, TurnContext, Attachment, TokenResponse, BotFrameworkAdapter, CardFactory,
MessageFactory, ActivityTypes, OAuthCard, SigninCard, ActionTypes
} from 'botbuilder';
import { PromptValidator } from './textPrompt';
import { sendPrompt } from './internal';
/**
* :package: **botbuilder-prompts**
*
* Defines settings for an OAuthPrompt.
*/
export interface OAuthPromptSettings {
/** Name of the OAuth connection being used. */
connectionName: string;
/** Title of the cards signin button. */
title: string;
/** (Optional) additional text to include on the signin card. */
text?: string;
}
/**
* :package: **botbuilder-prompts**
*
* Prompts the user to sign in using the Bot Frameworks Single Sign On (SSO) service.
*
* **Usage Example:**
*
* ```JavaScript
* const { createOAuthPrompt } = require('botbuilder-prompts');
*
* const loginPrompt = createOAuthPrompt({
* connectionName: 'GitConnection',
* title: 'Login To GitHub'
* });
* ```
* @param O (Optional) type of result returned by the [recognize()](#recognize) method. This defaults to an instance of `TokenResponse` but can be changed by the prompts custom validator.
*/
export interface OAuthPrompt<O = TokenResponse> {
/**
* Sends a formated prompt to the user.
*
* An `OAuthCard` will be automatically created and sent to the user requesting them to
* signin. If you need to localize the card or customize the message sent to the user for any
* reason you can pass in the `Activity` to send. This should just be an activity of type
* `message` and contain at least one attachment that's an `OAuthCard`.
*
* **Usage Example:**
*
* ```JavaScript
* await loginPrompt.prompt(context);
* ```
* @param context Context for the current turn of conversation.
* @param prompt (Optional) activity to send along the user. This should include an attachment containing an `OAuthCard`. If ommited, an activity will be automatically generated.
*/
prompt(context: TurnContext, prompt?: Partial<Activity>): Promise<void>;
/**
* Attempts to resolve the token after [prompt()](#prompt) has been called. There are two core
* flows that need to be supported to complete a users signin:
*
* - The automatic signin flow where the SSO service will forward the bot the users access
* token using either an `event` or `invoke` activity.
* - The "magic code" flow where a user is prompted by the SSO service to send the bot a six
* digit code confirming their identity. This code will be sent as a standard `message` activity.
*
* The `recognize()` method automatically handles both flows for the bot but you should ensure
* that you don't accidentally filter out the `event` and `invoke` activities before calling
* recognize(). Because of this we generally recommend you put the call to recognize() towards
* the beginning of your bot logic.
*
* You should also be prepared for the case where the user fails to enter the correct
* "magic code" or simply decides they don't want to click the signin button.
*
* **Usage Example:**
*
* ```JavaScript
* const token = await loginPrompt.recognize(context);
* if (token) {
* // Save token and continue.
* }
* ```
* @param context Context for the current turn of conversation.
* @param connectionName Name of the auth connection to use.
*/
recognize(context: TurnContext): Promise<O|undefined>;
/**
* Attempts to retrieve the cached token for a signed in user. You will generally want to call
* this before calling [prompt()](#prompt) to send the user a signin card.
*
* **Usage Example:**
*
* ```JavaScript
* const token = await loginPrompt.getUserToken(context);
* if (!token) {
* await loginPrompt.prompt(context);
* }
* ```
* @param context Context for the current turn of conversation.
*/
getUserToken(context: TurnContext): Promise<O|undefined>;
/**
* Signs the user out of the service.
*
* **Usage Example:**
*
* ```JavaScript
* await loginPrompt.signOutUser(context);
* ```
* @param context Context for the current turn of conversation.
*/
signOutUser(context: TurnContext): Promise<void>;
}
/**
* :package: **botbuilder-prompts**
*
* Creates a new prompt that asks the user to sign in using the Bot Frameworks Single Sign On (SSO)
* service.
*
* **Usage Example:**
*
* ```JavaScript
* async function ensureLogin(context, state, botLogic) {
* const now = new Date().getTime();
* if (state.token && now < (new Date(state.token.expiration).getTime() - 60000)) {
* return botLogic(context);
* } else {
* const loginPrompt = createOAuthPrompt({
* connectionName: 'GitConnection',
* title: 'Login To GitHub'
* });
* const token = await state.loginActive ? loginPrompt.recognize(context) : loginPrompt.getUserToken(context);
* if (token) {
* state.loginActive = false;
* state.token = token;
* return botLogic(context);
* } else if (context.activity.type === 'message') {
* if (!state.loginActive) {
* state.loginActive = true;
* state.loginStart = now;
* await loginPrompt.prompt(context);
* } else if (now >= (state.loginStart + (5 * 60 * 1000))) {
* state.loginActive = false;
* await context.sendActivity(`We're having a problem logging you in. Please try again later.`);
* }
* }
* }
* }
* ```
* @param O (Optional) type of result returned by the `recognize()` method. This defaults to an instance of `TokenResponse` but can be changed by the prompts custom validator.
* @param settings Configuration settings for the OAuthPrompt.
* @param validator (Optional) validator for providing additional validation logic or customizing the prompt sent to the user when invalid.
*/
export function createOAuthPrompt<O = TokenResponse>(settings: OAuthPromptSettings, validator?: PromptValidator<TokenResponse, O>): OAuthPrompt<O> {
return {
prompt: function prompt(context, prompt) {
try {
// Validate adapter type
if (!('getUserToken' in context.adapter)) { throw new Error(`OAuthPrompt.prompt(): not supported for the current adapter.`) }
// Format prompt
if (typeof prompt !== 'object') {
prompt = MessageFactory.attachment(CardFactory.oauthCard(
settings.connectionName,
settings.title,
settings.text
));
} else {
// Validate prompt
if (!Array.isArray(prompt.attachments)) { throw new Error(`OAuthPrompt.prompt(): supplied prompt missing attachments.`) }
const found = prompt.attachments.filter(a => a.contentType === CardFactory.contentTypes.oauthCard);
if (found.length == 0) { throw new Error(`OAuthPrompt.prompt(): supplied prompt missing OAuthCard.`) }
}
// Send prompt
return Promise.resolve(prompt)
.then((p) => {
switch (context.activity.channelId) {
case "msteams":
case "cortana":
case "skype":
case "skypeforbusiness":
return (context.adapter as BotFrameworkAdapter).getSignInLink(context, settings.connectionName).then((link) => {
(p.attachments as Attachment[]).forEach(a => {
if (a.contentType === CardFactory.contentTypes.oauthCard) {
const card: OAuthCard = a.content;
const title = card.buttons[0].title;
a.contentType = CardFactory.contentTypes.signinCard;
a.content = {
text: card.text,
buttons: [{ type: ActionTypes.Signin, title: title, value: link }]
} as SigninCard;
}
});
return p;
});
default:
return p;
}
})
.then((p) => sendPrompt(context, p));
} catch(err) {
return Promise.reject(err);
}
},
recognize: function recognize(context) {
// Validate adapter type
if (!('getUserToken' in context.adapter)) { throw new Error(`OAuthPrompt.recognize(): not supported for the current adapter.`) }
// Attempt to get the token
return Promise.resolve()
.then(() => {
const adapter = context.adapter as BotFrameworkAdapter;
if (isTokenResponseEvent(context)) {
return Promise.resolve(context.activity.value as TokenResponse);
} else if (isTeamsVerificationInvoke(context)) {
const code = context.activity.value.state;
return context.sendActivity({ type: 'invokeResponse', value: { status: 200 }})
.then(() => adapter.getUserToken(context, settings.connectionName, code));
} else if (context.activity.type === ActivityTypes.Message) {
const matched = /(\d{6})/.exec(context.activity.text);
if (matched && matched.length > 1) {
return adapter.getUserToken(context, settings.connectionName, matched[1]);
} else {
return Promise.resolve(undefined);
}
}
})
.then((value: TokenResponse|undefined) => validator ? validator(context, value) : value as any);
},
getUserToken: function getUserToken(context) {
// Validate adapter type
if (!('getUserToken' in context.adapter)) { throw new Error(`OAuthPrompt.getUserToken(): not supported for the current adapter.`) }
// Get the token and call validator
const adapter = context.adapter as BotFrameworkAdapter;
return adapter.getUserToken(context, settings.connectionName)
.then((value) => {
return Promise.resolve(validator ? validator(context, value) : value as any);
});
},
signOutUser: function signOutUser(context) {
// Validate adapter type
if (!('signOutUser' in context.adapter)) { throw new Error(`OAuthPrompt.signOutUser(): not supported for the current adapter.`) }
// Sign out user
const adapter = context.adapter as BotFrameworkAdapter;
return adapter.signOutUser(context, settings.connectionName);
}
};
}
function isTokenResponseEvent(context: TurnContext): boolean {
const a = context.activity;
return (a.type === ActivityTypes.Event && a.name === 'tokens/response')
}
function isTeamsVerificationInvoke(context: TurnContext): boolean {
const a = context.activity;
return (a.type === ActivityTypes.Invoke && a.name === 'signin/verifyState')
}