botbuilder-dialogs-adaptive
Version:
Rule system for the Microsoft BotBuilder dialog system.
347 lines • 16.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OAuthInput = exports.channels = void 0;
/**
* @module botbuilder-dialogs-adaptive
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const inputDialog_1 = require("./inputDialog");
const templates_1 = require("../templates");
const telemetryLoggerConstants_1 = require("../telemetryLoggerConstants");
const botbuilder_1 = require("botbuilder");
const adaptive_expressions_1 = require("adaptive-expressions");
const botbuilder_dialogs_1 = require("botbuilder-dialogs");
exports.channels = {
console: 'console',
cortana: 'cortana',
directline: 'directline',
email: 'email',
emulator: 'emulator',
facebook: 'facebook',
groupme: 'groupme',
kik: 'kik',
line: 'line',
msteams: 'msteams',
skype: 'skype',
skypeforbusiness: 'skypeforbusiness',
slack: 'slack',
sms: 'sms',
telegram: 'telegram',
webchat: 'webchat',
};
const persistedOptions = 'options';
const persistedState = 'state';
const persistedExpires = 'expires';
const attemptCountKey = 'attemptCount';
/**
* OAuthInput prompts user to login.
*/
class OAuthInput extends inputDialog_1.InputDialog {
/**
* Initializes a new instance of the [OAuthInput](xref:botbuilder-dialogs-adaptive.OAuthInput) class
*
* @param connectionName Optional. Name of the OAuth connection being used.
* @param title Optional. Title of the cards signin button.
* @param text Optional. Additional text to include on the signin card.
* @param timeout Optional. Number of milliseconds the prompt will wait for the user to authenticate.
*/
constructor(connectionName, title, text, timeout) {
super();
/**
* (Optional) number of milliseconds the prompt will wait for the user to authenticate.
* Defaults to a value `900,000` (15 minutes.)
*/
this.timeout = new adaptive_expressions_1.IntExpression(900000);
this.connectionName = new adaptive_expressions_1.StringExpression(connectionName);
this.title = new adaptive_expressions_1.StringExpression(title);
this.text = new adaptive_expressions_1.StringExpression(text);
if (timeout) {
this.timeout = new adaptive_expressions_1.IntExpression(timeout);
}
}
/**
* @param property The key of the conditional selector configuration.
* @returns The converter for the selector configuration.
*/
getConverter(property) {
switch (property) {
case 'connectionName':
return new adaptive_expressions_1.StringExpressionConverter();
case 'title':
return new adaptive_expressions_1.StringExpressionConverter();
case 'text':
return new adaptive_expressions_1.StringExpressionConverter();
case 'timeout':
return new adaptive_expressions_1.IntExpressionConverter();
default:
return super.getConverter(property);
}
}
/**
* Called when a prompt [Dialog](xref:botbuilder-dialogs.Dialog) is pushed onto the dialog stack and is being activated.
*
* @param dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation.
* @param options Optional. Additional information to pass to the prompt being started.
* @returns A [DialogTurnResult](xref:botbuilder-dialogs.DialogTurnResult) `Promise` representing the asynchronous operation.
*/
beginDialog(dc, options) {
return __awaiter(this, void 0, void 0, function* () {
if (this.disabled && this.disabled.getValue(dc.state)) {
return yield dc.endDialog();
}
// Ensure prompts have input hint set
const o = Object.assign({}, options);
if (o.prompt && typeof o.prompt === 'object' && typeof o.prompt.inputHint !== 'string') {
o.prompt.inputHint = botbuilder_1.InputHints.AcceptingInput;
}
if (o.retryPrompt && typeof o.retryPrompt === 'object' && typeof o.retryPrompt.inputHint !== 'string') {
o.retryPrompt.inputHint = botbuilder_1.InputHints.AcceptingInput;
}
const op = yield this.onInitializeOptions(dc, options);
dc.state.setValue(botbuilder_dialogs_1.ThisPath.options, op);
dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 0);
// If alwaysPrompt is set to true, then clear property value for turn 0.
if (this.property && this.alwaysPrompt && this.alwaysPrompt.getValue(dc.state)) {
dc.state.deleteValue(this.property.getValue(dc.state));
}
// Initialize prompt state
const state = dc.activeDialog.state;
state[persistedOptions] = o;
state[persistedState] = {};
state[persistedState][attemptCountKey] = 0;
state[persistedExpires] = new Date().getTime() + this.timeout.getValue(dc.state) || 900000;
// Attempt to get the users token
const output = yield this.getUserToken(dc);
if (output !== undefined) {
// Set token into token property
if (this.property) {
dc.state.setValue(this.property.getValue(dc.state), output);
}
// Return token
return yield dc.endDialog(output);
}
else {
dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 1);
// Prompt user to login
yield this.sendOAuthCard(dc, state.options.prompt);
return botbuilder_dialogs_1.Dialog.EndOfTurn;
}
});
}
/**
* Called when a prompt [Dialog](xref:botbuilder-dialogs.Dialog) is the active dialog and the user replied with a new activity.
*
* @param dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation.
* @returns A [DialogTurnResult](xref:botbuilder-dialogs.DialogTurnResult) `Promise` representing the asynchronous operation.
*/
continueDialog(dc) {
return __awaiter(this, void 0, void 0, function* () {
if (!dc) {
throw new Error('Missing DialogContext');
}
const interrupted = dc.state.getValue(botbuilder_dialogs_1.TurnPath.interrupted, false);
const turnCount = dc.state.getValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 0);
// Recognize token
const recognized = yield this.recognizeToken(dc);
// Check for timeout
const state = dc.activeDialog.state;
const expires = state[persistedExpires];
const isMessage = dc.context.activity.type === botbuilder_1.ActivityTypes.Message;
const isTimeoutActivityType = isMessage ||
this.isTokenResponseEvent(dc.context) ||
this.isTeamsVerificationInvoke(dc.context) ||
this.isTokenExchangeRequestInvoke(dc.context);
const hasTimedOut = isTimeoutActivityType && new Date().getTime() > expires;
if (hasTimedOut) {
if (this.property) {
dc.state.deleteValue(this.property.getValue(dc.state));
}
return yield dc.endDialog(undefined);
}
else {
const promptState = state[persistedState];
const promptOptions = state[persistedOptions];
promptState[attemptCountKey] += 1;
// Validate the return value
let inputState = inputDialog_1.InputState.invalid;
if (recognized.succeeded) {
inputState = inputDialog_1.InputState.valid;
}
// Return recognized value or re-prompt
if (inputState === inputDialog_1.InputState.valid) {
// Set token into token property
if (this.property) {
dc.state.setValue(this.property.getValue(dc.state), recognized.value);
}
return yield dc.endDialog(recognized.value);
}
else if (!this.maxTurnCount || turnCount < this.maxTurnCount.getValue(dc.state)) {
if (!interrupted) {
// increase the turnCount as last step
dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, turnCount + 1);
if (isMessage) {
const prompt = yield this.onRenderPrompt(dc, inputState);
yield dc.context.sendActivity(prompt);
}
}
if (isMessage) {
yield this.sendOAuthCard(dc, promptOptions === null || promptOptions === void 0 ? void 0 : promptOptions.prompt);
}
return botbuilder_dialogs_1.Dialog.EndOfTurn;
}
else {
if (this.defaultValue) {
const { value } = this.defaultValue.tryGetValue(dc.state);
if (this.defaultValueResponse) {
const response = yield this.defaultValueResponse.bind(dc, dc.state);
const properties = {
template: JSON.stringify(this.defaultValueResponse),
result: response ? JSON.stringify(response) : '',
context: telemetryLoggerConstants_1.TelemetryLoggerConstants.OAuthInputResultEvent,
};
this.telemetryClient.trackEvent({
name: telemetryLoggerConstants_1.TelemetryLoggerConstants.GeneratorResultEvent,
properties,
});
yield dc.context.sendActivity(response);
}
// set output property
dc.state.setValue(this.property.getValue(dc.state), value);
return yield dc.endDialog(value);
}
}
return yield dc.endDialog();
}
});
}
/**
* Attempts to retrieve the stored token for the current user.
*
* @param dc Context reference the user that's being looked up.
* @param code (Optional) login code received from the user.
* @returns A promise representing the asynchronous operation.
*/
getUserToken(dc, code) {
return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, {
title: undefined,
connectionName: this.connectionName.getValue(dc.state),
}).getUserToken(dc.context, code);
}
/**
* Signs the user out of the service.
*
* @remarks
* This example shows creating an instance of the prompt and then signing out the user.
*
* ```JavaScript
* const prompt = new OAuthPrompt({
* connectionName: 'GitConnection',
* title: 'Login To GitHub'
* });
* await prompt.signOutUser(context);
* ```
* @param dc Context referencing the user that's being signed out.
* @returns A promise representing the asynchronous operation.
*/
signOutUser(dc) {
return __awaiter(this, void 0, void 0, function* () {
return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, {
title: undefined,
connectionName: this.connectionName.getValue(dc.state),
}).signOutUser(dc.context);
});
}
onComputeId() {
return `OAuthInput[${this.prompt && this.prompt.toString()}]`;
}
/**
* @protected
* Called when input has been received.
* @param _dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation.
*/
onRecognizeInput(_dc) {
throw new Error('Method not implemented.');
}
sendOAuthCard(dc, prompt) {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
// Save state prior to sending OAuthCard: the invoke response for a token exchange from the root bot could come
// in before this method ends or could land in another instance in scale-out scenarios, which means that if the
// state is not saved, the OAuthInput would not be at the top of the stack, and the token exchange invoke would
// get discarded.
const conversationState = dc.context.turnState.get('ConversationState');
if (conversationState) {
yield conversationState.saveChanges(dc.context, false);
}
// Prepare oauth card
let title = (_a = (yield new templates_1.TextTemplate(this.title.expressionText).bind(dc, dc.state))) !== null && _a !== void 0 ? _a : this.title.getValue(dc.state);
if (title === null || title === void 0 ? void 0 : title.startsWith('=')) {
title = (_b = adaptive_expressions_1.Expression.parse(title).tryEvaluate(dc.state)) === null || _b === void 0 ? void 0 : _b.value;
}
let text = (_c = (yield new templates_1.TextTemplate(this.text.expressionText).bind(dc, dc.state))) !== null && _c !== void 0 ? _c : this.text.getValue(dc.state);
if (text === null || text === void 0 ? void 0 : text.startsWith('=')) {
text = (_d = adaptive_expressions_1.Expression.parse(text).tryEvaluate(dc.state)) === null || _d === void 0 ? void 0 : _d.value;
}
const settings = {
connectionName: (_e = this.connectionName) === null || _e === void 0 ? void 0 : _e.getValue(dc.state),
title,
text,
};
// Send OAuthCard to root bot. The root bot could attempt to do a token exchange or if it cannot do token
// exchange for this connection it will let the card get to the user to allow them to sign in.
return botbuilder_dialogs_1.OAuthPrompt.sendOAuthCard(settings, dc.context, prompt);
});
}
recognizeToken(dc) {
return __awaiter(this, void 0, void 0, function* () {
return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, {
title: undefined,
connectionName: this.connectionName.getValue(dc.state),
}).recognizeToken(dc);
});
}
isTokenResponseEvent(context) {
const activity = context.activity;
return activity.type === botbuilder_1.ActivityTypes.Event && activity.name === botbuilder_1.tokenResponseEventName;
}
isTeamsVerificationInvoke(context) {
const activity = context.activity;
return activity.type === botbuilder_1.ActivityTypes.Invoke && activity.name === botbuilder_1.verifyStateOperationName;
}
isTokenExchangeRequestInvoke(context) {
const activity = context.activity;
return activity.type === botbuilder_1.ActivityTypes.Invoke && activity.name === botbuilder_1.tokenExchangeOperationName;
}
isTokenExchangeRequest(obj) {
if (Object.hasOwnProperty.call(obj, 'token')) {
return true;
}
return false;
}
sendInvokeResponse(turnContext, status, body) {
return __awaiter(this, void 0, void 0, function* () {
yield turnContext.sendActivity({
type: 'invokeResponse',
value: {
status,
body,
},
});
});
}
}
exports.OAuthInput = OAuthInput;
OAuthInput.$kind = 'Microsoft.OAuthInput';
//# sourceMappingURL=oauthInput.js.map